Cleaned up several tile properties; added railroad adjusting
This commit is contained in:
parent
72cba627a8
commit
f0680ce0c4
@ -34,6 +34,8 @@ export const DIRECTIONS = {
|
||||
opposite: 'west',
|
||||
},
|
||||
};
|
||||
// Should match the bit ordering above, and CC2's order
|
||||
export const DIRECTION_ORDER = ['north', 'east', 'south', 'west'];
|
||||
|
||||
// TODO cc2 order is: swivel, thinwalls, canopy (and yes you can have them all in the same tile)
|
||||
export const DRAW_LAYERS = {
|
||||
|
||||
@ -1,11 +1,10 @@
|
||||
import { TransientOverlay } from './main-base.js';
|
||||
import { mk } from './util.js';
|
||||
import { mk, mk_svg } from './util.js';
|
||||
|
||||
// FIXME could very much stand to have a little animation when appearing
|
||||
class TileEditorOverlay extends TransientOverlay {
|
||||
constructor(conductor) {
|
||||
let root = mk('form.editor-popup-tile-editor');
|
||||
root.append(mk('span.popup-chevron'));
|
||||
super(conductor, root);
|
||||
this.editor = conductor.editor;
|
||||
this.tile = null;
|
||||
@ -24,34 +23,41 @@ class LetterTileEditor extends TileEditorOverlay {
|
||||
constructor(conductor) {
|
||||
super(conductor);
|
||||
|
||||
this.root.append(mk('h3', "Letter tile"));
|
||||
let list = mk('ol.editor-letter-tile-picker');
|
||||
this.root.append(list);
|
||||
this.glyph_elements = {};
|
||||
for (let c = 32; c < 128; c++) {
|
||||
let glyph = String.fromCharCode(c);
|
||||
let add = glyph => {
|
||||
let input = mk('input', {type: 'radio', name: 'glyph', value: glyph});
|
||||
this.glyph_elements[glyph] = input;
|
||||
let item = mk('li', mk('label', input, mk('span.-glyph', glyph)));
|
||||
list.append(item);
|
||||
};
|
||||
let arrows = ["⬆", "➡", "⬇", "⬅"];
|
||||
for (let c = 32; c < 96; c++) {
|
||||
let glyph = String.fromCharCode(c);
|
||||
add(glyph);
|
||||
// Add the arrows to the ends of the rows
|
||||
if (c % 16 === 15) {
|
||||
add(arrows[(c - 47) / 16]);
|
||||
}
|
||||
}
|
||||
|
||||
list.addEventListener('change', ev => {
|
||||
let glyph = this.root.elements['glyph'].value;
|
||||
if (this.tile) {
|
||||
this.tile.ascii_code = glyph.charCodeAt(0);
|
||||
// FIXME should be able to mark tiles as dirty, also this is sure a mouthful
|
||||
this.conductor.editor.renderer.draw();
|
||||
this.tile.overlaid_glyph = this.root.elements['glyph'].value;
|
||||
this.editor.mark_tile_dirty(this.tile);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
edit_tile(tile) {
|
||||
super.edit_tile(tile);
|
||||
this.root.elements['glyph'].value = String.fromCharCode(tile.ascii_code);
|
||||
this.root.elements['glyph'].value = tile.overlaid_glyph;
|
||||
}
|
||||
|
||||
static configure_tile_defaults(tile) {
|
||||
tile.ascii_code = 32;
|
||||
tile.type.populate_defaults(tile);
|
||||
}
|
||||
}
|
||||
|
||||
@ -59,29 +65,110 @@ class HintTileEditor extends TileEditorOverlay {
|
||||
constructor(conductor) {
|
||||
super(conductor);
|
||||
|
||||
this.root.append(mk('h3', "Hint text"));
|
||||
this.text = mk('textarea.editor-hint-tile-text');
|
||||
this.root.append(this.text);
|
||||
this.text.addEventListener('input', ev => {
|
||||
if (this.tile) {
|
||||
this.tile.specific_hint = this.text.value;
|
||||
this.tile.hint_text = this.text.value;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
edit_tile(tile) {
|
||||
super.edit_tile(tile);
|
||||
this.text.value = tile.specific_hint ?? "";
|
||||
this.text.value = tile.hint_text ?? "";
|
||||
}
|
||||
|
||||
static configure_tile_defaults(tile) {
|
||||
tile.specific_hint = "";
|
||||
tile.hint_text = "";
|
||||
}
|
||||
}
|
||||
|
||||
class RailroadTileEditor extends TileEditorOverlay {
|
||||
constructor(conductor) {
|
||||
super(conductor);
|
||||
|
||||
let svg_icons = [];
|
||||
for (let center of [[16, 0], [16, 16], [0, 16], [0, 0]]) {
|
||||
let symbol = mk_svg('svg', {viewBox: '0 0 16 16'},
|
||||
mk_svg('circle', {cx: center[0], cy: center[1], r: 3}),
|
||||
mk_svg('circle', {cx: center[0], cy: center[1], r: 13}),
|
||||
);
|
||||
svg_icons.push(symbol);
|
||||
}
|
||||
svg_icons.push(mk_svg('svg', {viewBox: '0 0 16 16'},
|
||||
mk_svg('rect', {x: -2, y: 3, width: 20, height: 10}),
|
||||
));
|
||||
svg_icons.push(mk_svg('svg', {viewBox: '0 0 16 16'},
|
||||
mk_svg('rect', {x: 3, y: -2, width: 10, height: 20}),
|
||||
));
|
||||
|
||||
this.root.append(mk('h3', "Tracks"));
|
||||
let track_list = mk('ul.editor-railroad-tile-tracks');
|
||||
// Shown as two rows, this puts the straight parts first and the rest in a circle
|
||||
let track_order = [4, 1, 2, 5, 0, 3];
|
||||
for (let i of track_order) {
|
||||
let input = mk('input', {type: 'checkbox', name: 'track', value: i});
|
||||
track_list.append(mk('li', mk('label', input, svg_icons[i])));
|
||||
}
|
||||
track_list.addEventListener('change', ev => {
|
||||
if (this.tile) {
|
||||
let bit = 1 << ev.target.value;
|
||||
if (ev.target.checked) {
|
||||
this.tile.tracks |= bit;
|
||||
}
|
||||
else {
|
||||
this.tile.tracks &= ~bit;
|
||||
}
|
||||
this.editor.mark_tile_dirty(this.tile);
|
||||
}
|
||||
});
|
||||
this.root.append(track_list);
|
||||
|
||||
this.root.append(mk('h3', "Switch"));
|
||||
let switch_list = mk('ul.editor-railroad-tile-tracks.--switch');
|
||||
for (let i of track_order) {
|
||||
let input = mk('input', {type: 'radio', name: 'switch', value: i});
|
||||
switch_list.append(mk('li', mk('label', input, svg_icons[i].cloneNode(true))));
|
||||
}
|
||||
// TODO if they remove a track it should change the switch
|
||||
// TODO if they pick a track that's missing it should add it
|
||||
switch_list.addEventListener('change', ev => {
|
||||
if (this.tile) {
|
||||
this.tile.track_switch = ev.target.value;
|
||||
this.editor.mark_tile_dirty(this.tile);
|
||||
}
|
||||
});
|
||||
this.root.append(switch_list);
|
||||
|
||||
// TODO need a way to set no actor at all
|
||||
// TODO initial actor facing (maybe only if there's an actor in the cell)
|
||||
}
|
||||
|
||||
edit_tile(tile) {
|
||||
super.edit_tile(tile);
|
||||
|
||||
for (let input of this.root.elements['track']) {
|
||||
input.checked = !! (tile.tracks & (1 << input.value));
|
||||
}
|
||||
|
||||
if (tile.track_switch === null) {
|
||||
this.root.elements['switch'].value = '';
|
||||
}
|
||||
else {
|
||||
this.root.elements['switch'].value = tile.track_switch;
|
||||
}
|
||||
}
|
||||
|
||||
static configure_tile_defaults(tile) {
|
||||
}
|
||||
}
|
||||
|
||||
export const TILES_WITH_PROPS = {
|
||||
floor_letter: LetterTileEditor,
|
||||
hint: HintTileEditor,
|
||||
railroad: RailroadTileEditor,
|
||||
// TODO various wireable tiles
|
||||
// TODO initial value of counter
|
||||
// TODO cloner arrows
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import { DIRECTIONS } from './defs.js';
|
||||
import { DIRECTIONS, DIRECTION_ORDER } from './defs.js';
|
||||
import * as format_base from './format-base.js';
|
||||
import TILE_TYPES from './tiletypes.js';
|
||||
import * as util from './util.js';
|
||||
@ -374,11 +374,28 @@ const TILE_ENCODING = {
|
||||
modifier: {
|
||||
_parts: ['ne', 'se', 'sw', 'ne', 'ew', 'ns'],
|
||||
decode(tile, mask) {
|
||||
tile.railroad_bits = mask;
|
||||
// Leave the track parts alone as a bitmask; the type has a list of them
|
||||
tile.tracks = mask & 0x3f;
|
||||
// Check for a switch, which is a bit number in the above mask
|
||||
if (mask & 0x40) {
|
||||
tile.track_switch = (mask >> 8) & 0x0f;
|
||||
}
|
||||
else {
|
||||
tile.track_switch = null;
|
||||
}
|
||||
// Initial actor facing is in the highest nybble
|
||||
tile.entered_direction = (mask >> 12) & 0x03;
|
||||
},
|
||||
encode(tile) {
|
||||
// TODO
|
||||
return 0;
|
||||
let ret = tile.tracks & 0x3f;
|
||||
if (tile.track_switch !== null) {
|
||||
ret |= 0x40;
|
||||
ret |= tile.track_switch << 8;
|
||||
}
|
||||
if (tile.entered_direction) {
|
||||
ret |= DIRECTION_ORDER.indexOf(tile.entered_direction) << 12;
|
||||
}
|
||||
return ret;
|
||||
},
|
||||
},
|
||||
},
|
||||
@ -519,10 +536,25 @@ const TILE_ENCODING = {
|
||||
name: 'floor_letter',
|
||||
modifier: {
|
||||
decode(tile, ascii_code) {
|
||||
tile.ascii_code = ascii_code;
|
||||
if (ascii_code < 28 || ascii_code >= 96) {
|
||||
// Invalid
|
||||
tile.overlaid_glyph = "?";
|
||||
}
|
||||
else if (ascii_code < 32) {
|
||||
// Arrows are stored goofily
|
||||
tile.overlaid_glyph = ["⬆", "➡", "⬇", "⬅"][ascii_code - 28];
|
||||
}
|
||||
else {
|
||||
tile.overlaid_glyph = String.fromCharCode(ascii_code);
|
||||
}
|
||||
},
|
||||
encode(tile) {
|
||||
return tile.ascii_code;
|
||||
let arrow_index = ["⬆", "➡", "⬇", "⬅"].indexOf(tile.overlaid_glyph);
|
||||
if (arrow_index >= 0) {
|
||||
return arrow_index + 28;
|
||||
}
|
||||
|
||||
return tile.overlaid_glyph.charCodeAt(0);
|
||||
},
|
||||
},
|
||||
},
|
||||
@ -798,11 +830,10 @@ export function parse_level(buf, number = 1) {
|
||||
level.hint = str;
|
||||
}
|
||||
else if (type === 'NOTE') {
|
||||
// Author's comments... but might also include multiple hints
|
||||
// for levels with multiple hint tiles, delineated by [CLUE].
|
||||
// For my purposes, extra hints are associated with the
|
||||
// individual tiles, so we'll map those later
|
||||
[level.comment, ...extra_hints] = str.split(/^\[CLUE\]$/mg);
|
||||
// Author's comments... but might also include multiple hints for levels with
|
||||
// multiple hint tiles, delineated by [CLUE] (anywhere in the line (!)).
|
||||
// LL treats extra hints as tile properties, so store them for later
|
||||
[level.comment, ...extra_hints] = str.split(/\n?^.*\[CLUE\].*$\n?/mg);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
@ -1018,13 +1049,14 @@ export function parse_level(buf, number = 1) {
|
||||
}
|
||||
|
||||
// Connect extra hints
|
||||
let h = 0;
|
||||
for (let tile of hint_tiles) {
|
||||
if (h > extra_hints.length)
|
||||
break;
|
||||
|
||||
tile.specific_hint = extra_hints[h];
|
||||
h++;
|
||||
for (let [i, tile] of hint_tiles.entries()) {
|
||||
if (i < extra_hints.length) {
|
||||
tile.hint_text = extra_hints[i];
|
||||
}
|
||||
else {
|
||||
// Fall back to regular hint
|
||||
tile.hint_text = null;
|
||||
}
|
||||
}
|
||||
|
||||
return level;
|
||||
|
||||
@ -297,7 +297,7 @@ export class Level {
|
||||
let tile = Tile.from_template(template_tile);
|
||||
if (tile.type.is_hint) {
|
||||
// Copy over the tile-specific hint, if any
|
||||
tile.specific_hint = template_tile.specific_hint ?? null;
|
||||
tile.hint_text = template_tile.hint_text ?? null;
|
||||
}
|
||||
|
||||
if (tile.type.is_power_source) {
|
||||
@ -390,7 +390,7 @@ export class Level {
|
||||
tile.type.on_ready(tile, this);
|
||||
}
|
||||
if (cell === this.player.cell && tile.type.is_hint) {
|
||||
this.hint_shown = tile.specific_hint ?? this.stored_level.hint;
|
||||
this.hint_shown = tile.hint_text ?? this.stored_level.hint;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1019,7 +1019,7 @@ export class Level {
|
||||
}
|
||||
|
||||
if (actor === this.player && tile.type.is_hint) {
|
||||
this.hint_shown = tile.specific_hint ?? this.stored_level.hint;
|
||||
this.hint_shown = tile.hint_text ?? this.stored_level.hint;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -284,7 +284,7 @@ class TrackOperation extends DrawOperation {
|
||||
|
||||
// Get the corresponding bit
|
||||
let bit = null;
|
||||
for (let [i, track] of TILE_TYPES['railroad']._track_order.entries()) {
|
||||
for (let [i, track] of TILE_TYPES['railroad'].track_order.entries()) {
|
||||
if ((track[0] === this.entry_direction && track[1] === exit_direction) ||
|
||||
(track[1] === this.entry_direction && track[0] === exit_direction))
|
||||
{
|
||||
@ -300,10 +300,12 @@ class TrackOperation extends DrawOperation {
|
||||
let cell = this.cell(prevx, prevy);
|
||||
let terrain = cell[0];
|
||||
if (terrain.type.name === 'railroad') {
|
||||
terrain.railroad_bits |= bit;
|
||||
terrain.tracks |= bit;
|
||||
}
|
||||
else {
|
||||
terrain = { type: TILE_TYPES['railroad'], railroad_bits: bit };
|
||||
terrain = { type: TILE_TYPES['railroad'] };
|
||||
terrain.type.populate_defaults(terrain);
|
||||
terrain.tracks |= bit;
|
||||
this.editor.place_in_cell(prevx, prevy, terrain);
|
||||
}
|
||||
|
||||
@ -364,17 +366,19 @@ class AdjustOperation extends MouseOperation {
|
||||
for (let tile of cell) {
|
||||
// Rotate railroads, which are a bit complicated
|
||||
if (tile.type.name === 'railroad') {
|
||||
let new_bits = 0;
|
||||
for (let [i, new_bit] of [1, 2, 3, 0, 5, 4].entries()) {
|
||||
if (tile.railroad_bits & (1 << i)) {
|
||||
new_bits |= (1 << new_bit);
|
||||
let new_tracks = 0;
|
||||
let rotated_tracks = [1, 2, 3, 0, 5, 4];
|
||||
for (let [i, new_bit] of rotated_tracks.entries()) {
|
||||
if (tile.tracks & (1 << i)) {
|
||||
new_tracks |= (1 << new_bit);
|
||||
}
|
||||
}
|
||||
if (tile.railroad_bits & 0x40) {
|
||||
new_bits |= 0x40;
|
||||
tile.tracks = new_tracks;
|
||||
|
||||
if (tile.switch_track !== null) {
|
||||
tile.switch_track = rotated_tracks[tile.switch_track];
|
||||
}
|
||||
// TODO high byte also
|
||||
tile.railroad_bits = new_bits;
|
||||
tile.entered_direction = DIRECTIONS[tile.entered_direction].right;
|
||||
}
|
||||
|
||||
// TODO also directional blocks
|
||||
@ -832,7 +836,6 @@ export class Editor extends PrimaryView {
|
||||
this.selected_tile_el.addEventListener('click', ev => {
|
||||
if (this.palette_selection && TILES_WITH_PROPS[this.palette_selection.type.name]) {
|
||||
// FIXME use tile bounds
|
||||
// FIXME redraw the tile after editing
|
||||
this.open_tile_prop_overlay(this.palette_selection, ev.clientX, ev.clientY);
|
||||
}
|
||||
});
|
||||
@ -973,11 +976,6 @@ export class Editor extends PrimaryView {
|
||||
name = tile.type.name;
|
||||
}
|
||||
|
||||
// FIXME should redraw in an existing canvas
|
||||
this.selected_tile_el.textContent = '';
|
||||
// FIXME should draw the actual tile!!
|
||||
this.selected_tile_el.append(this.renderer.create_tile_type_canvas(name, tile));
|
||||
|
||||
if (this.palette_selection) {
|
||||
let entry = this.palette[this.palette_selection.type.name];
|
||||
if (entry) {
|
||||
@ -989,6 +987,8 @@ export class Editor extends PrimaryView {
|
||||
this.palette[name].classList.add('--selected');
|
||||
}
|
||||
|
||||
this.mark_tile_dirty(tile);
|
||||
|
||||
// Some tools obviously don't work with a palette selection, in which case changing tiles
|
||||
// should default you back to the pencil
|
||||
if (this.current_tool === 'adjust') {
|
||||
@ -996,6 +996,18 @@ export class Editor extends PrimaryView {
|
||||
}
|
||||
}
|
||||
|
||||
mark_tile_dirty(tile) {
|
||||
// TODO partial redraws! until then, redraw everything
|
||||
if (tile === this.palette_selection) {
|
||||
// FIXME should redraw in an existing canvas
|
||||
this.selected_tile_el.textContent = '';
|
||||
this.selected_tile_el.append(this.renderer.create_tile_type_canvas(tile.type.name, tile));
|
||||
}
|
||||
else {
|
||||
this.renderer.draw();
|
||||
}
|
||||
}
|
||||
|
||||
is_in_bounds(x, y) {
|
||||
return 0 <= x && x < this.stored_level.size_x && 0 <= y && y < this.stored_level.size_y;
|
||||
}
|
||||
@ -1051,13 +1063,13 @@ export class Editor extends PrimaryView {
|
||||
// Horizontal position: centered, but kept within the screen
|
||||
let left;
|
||||
let margin = 8; // prefer to not quite touch the edges
|
||||
let halfwidth = root.offsetWidth / 2 + margin;
|
||||
if (document.body.clientWidth / 2 < halfwidth) {
|
||||
if (document.body.clientWidth < root.offsetWidth + margin * 2) {
|
||||
// It doesn't fit on the screen at all, so there's nothing we can do; just center it
|
||||
left = (document.body.clientWidth - root.offsetWidth) / 2;
|
||||
}
|
||||
else {
|
||||
left = Math.max(margin, Math.min(document.body.clientWidth - halfwidth, x0 - halfwidth));
|
||||
left = Math.max(margin, Math.min(document.body.clientWidth - root.offsetWidth - margin,
|
||||
x0 - root.offsetWidth / 2));
|
||||
}
|
||||
root.style.left = `${left}px`;
|
||||
root.style.setProperty('--chevron-offset', `${x0 - left}px`);
|
||||
|
||||
@ -63,18 +63,25 @@ export const CC2_TILESET_LAYOUT = {
|
||||
wall_invisible: [0, 2],
|
||||
wall_appearing: [0, 2],
|
||||
wall: [1, 2],
|
||||
floor_letter: [2, 2],
|
||||
'floor_letter#ascii': {
|
||||
floor_letter: {
|
||||
special: 'letter',
|
||||
base: [2, 2],
|
||||
letter_glyphs: {
|
||||
// Arrows
|
||||
"⬆": [14, 31],
|
||||
"➡": [14.5, 31],
|
||||
"⬇": [15, 31],
|
||||
"⬅": [15.5, 31],
|
||||
},
|
||||
letter_ranges: [{
|
||||
// ASCII text (only up through uppercase)
|
||||
range: [32, 96],
|
||||
x0: 0,
|
||||
y0: 0,
|
||||
width: 16,
|
||||
height: 1,
|
||||
},
|
||||
'floor_letter#arrows': {
|
||||
north: [14, 31],
|
||||
east: [14.5, 31],
|
||||
south: [15, 31],
|
||||
west: [15.5, 31],
|
||||
w: 0.5,
|
||||
h: 0.5,
|
||||
columns: 32,
|
||||
}],
|
||||
},
|
||||
thief_tools: [3, 2],
|
||||
socket: [4, 2],
|
||||
@ -830,6 +837,31 @@ export class Tileset {
|
||||
blit(coords[0], coords[1], ...mask);
|
||||
}
|
||||
|
||||
_draw_letter(drawspec, tile, tic, blit) {
|
||||
this._draw_standard(drawspec.base, tile, tic, blit);
|
||||
|
||||
let glyph = tile.overlaid_glyph;
|
||||
if (drawspec.letter_glyphs[glyph]) {
|
||||
let [x, y] = drawspec.letter_glyphs[glyph];
|
||||
// XXX size is hardcoded here, but not below, meh
|
||||
blit(x, y, 0, 0, 0.5, 0.5, 0.25, 0.25);
|
||||
}
|
||||
else {
|
||||
// Look for a range
|
||||
let u = glyph.charCodeAt(0);
|
||||
for (let rangedef of drawspec.letter_ranges) {
|
||||
if (rangedef.range[0] <= u && u < rangedef.range[1]) {
|
||||
let t = u - rangedef.range[0];
|
||||
let x = rangedef.x0 + rangedef.w * (t % rangedef.columns);
|
||||
let y = rangedef.y0 + rangedef.h * Math.floor(t / rangedef.columns);
|
||||
blit(x, y, 0, 0, rangedef.w, rangedef.h,
|
||||
(1 - rangedef.w) / 2, (1 - rangedef.h) / 2);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_draw_logic_gate(drawspec, tile, tic, blit) {
|
||||
// Layer 1: wiring state
|
||||
// Always draw the unpowered wire base
|
||||
@ -876,15 +908,15 @@ export class Tileset {
|
||||
let visible_parts = [];
|
||||
let topmost_part = null;
|
||||
for (let [i, part] of part_order.entries()) {
|
||||
if (tile && (tile.railroad_bits & (1 << i))) {
|
||||
if ((tile.railroad_bits >> 8) === i) {
|
||||
if (tile && (tile.tracks & (1 << i))) {
|
||||
if (tile.track_switch === i) {
|
||||
topmost_part = part;
|
||||
}
|
||||
visible_parts.push(part);
|
||||
}
|
||||
}
|
||||
|
||||
let has_switch = (tile && (tile.railroad_bits & 0x40));
|
||||
let has_switch = (tile && tile.track_switch !== null);
|
||||
for (let part of visible_parts) {
|
||||
this._draw_standard(drawspec.railroad_ties[part], tile, tic, blit);
|
||||
}
|
||||
@ -929,7 +961,11 @@ export class Tileset {
|
||||
|
||||
// TODO shift everything to use this style, this is ridiculous
|
||||
if (drawspec.special) {
|
||||
if (drawspec.special === 'logic-gate') {
|
||||
if (drawspec.special === 'letter') {
|
||||
this._draw_letter(drawspec, tile, tic, blit);
|
||||
return;
|
||||
}
|
||||
else if (drawspec.special === 'logic-gate') {
|
||||
this._draw_logic_gate(drawspec, tile, tic, blit);
|
||||
return;
|
||||
}
|
||||
@ -1144,36 +1180,6 @@ export class Tileset {
|
||||
}
|
||||
blit(x, y, x0, y0, x1 - x0, y1 - y0);
|
||||
}
|
||||
|
||||
// Special behavior for special objects
|
||||
// TODO? hardcode this less?
|
||||
if (name === 'floor_letter' && tile) {
|
||||
let n = tile.ascii_code - 32;
|
||||
let scale = 0.5;
|
||||
let sx, sy;
|
||||
if (n < 0) {
|
||||
// Arrows
|
||||
if (n < -4) {
|
||||
// Default to south
|
||||
n = -2;
|
||||
}
|
||||
|
||||
let direction = ['north', 'east', 'south', 'west'][n + 4];
|
||||
[sx, sy] = this.layout['floor_letter#arrows'][direction];
|
||||
}
|
||||
else {
|
||||
// ASCII text (only up through uppercase)
|
||||
let letter_spec = this.layout['floor_letter#ascii'];
|
||||
if (n > letter_spec.width / scale * letter_spec.height / scale) {
|
||||
n = 0;
|
||||
}
|
||||
let w = letter_spec.width / scale;
|
||||
sx = (letter_spec.x0 + n % w) * scale;
|
||||
sy = (letter_spec.y0 + Math.floor(n / w)) * scale;
|
||||
}
|
||||
let offset = (1 - scale) / 2;
|
||||
blit(sx, sy, 0, 0, 0.5, 0.5, offset, offset);
|
||||
}
|
||||
}
|
||||
|
||||
_rotate(direction, x0, y0, x1, y1) {
|
||||
|
||||
@ -90,6 +90,9 @@ const TILE_TYPES = {
|
||||
},
|
||||
floor_letter: {
|
||||
draw_layer: DRAW_LAYERS.terrain,
|
||||
populate_defaults(me) {
|
||||
me.overlaid_glyph = "?";
|
||||
},
|
||||
},
|
||||
// TODO possibly this should be a single tile
|
||||
floor_custom_green: {
|
||||
@ -316,7 +319,7 @@ const TILE_TYPES = {
|
||||
// Railroad
|
||||
railroad: {
|
||||
draw_layer: DRAW_LAYERS.terrain,
|
||||
_track_order: [
|
||||
track_order: [
|
||||
['north', 'east'],
|
||||
['south', 'east'],
|
||||
['south', 'west'],
|
||||
@ -324,15 +327,12 @@ const TILE_TYPES = {
|
||||
['east', 'west'],
|
||||
['north', 'south'],
|
||||
],
|
||||
// FIXME railroad_bits sucks, split into some useful variables
|
||||
on_ready(me) {
|
||||
// If there's already an actor on top of us, assume it entered the way it's already
|
||||
// facing (which may be illegal, in which case it can't leave)
|
||||
// FIXME wrong! > Yeah, so in the high byte, the low nibble encodes the active track. What's missing is that the high nibble encodes a direction value, which is required when a mob starts out on top of the track: it represents the direction the mob was going when it entered the tile, which controls which way it can go on the track.
|
||||
let actor = me.cell.get_actor();
|
||||
if (actor) {
|
||||
me.entered_direction = actor.direction;
|
||||
}
|
||||
populate_defaults(me) {
|
||||
me.tracks = 0; // bitmask of bits 0-5, corresponding to track order above
|
||||
me.track_switch = null; // null, or 0-5 indicating the active switched track
|
||||
// If there's already an actor on us, it's treated as though it entered the tile moving
|
||||
// in this direction, which is given in the save file and defaults to zero i.e. north
|
||||
me.entered_direction = 'north';
|
||||
},
|
||||
// TODO feel like "ignores" was the wrong idea and there should just be some magic flags for
|
||||
// particular objects that can be immune to. or maybe those objects should have their own
|
||||
@ -345,28 +345,28 @@ const TILE_TYPES = {
|
||||
return true;
|
||||
},
|
||||
*_iter_tracks(me) {
|
||||
let order = me.type._track_order;
|
||||
if (me.railroad_bits & 0x40) {
|
||||
let order = me.type.track_order;
|
||||
if (me.track_switch !== null) {
|
||||
// FIXME what happens if the "top" track is not actually a valid track???
|
||||
yield order[me.railroad_bits >> 8];
|
||||
yield order[me.track_switch];
|
||||
}
|
||||
else {
|
||||
for (let [i, track] of order.entries()) {
|
||||
if (me.railroad_bits & (1 << i)) {
|
||||
if (me.tracks & (1 << i)) {
|
||||
yield track;
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
_switch_track(me, level) {
|
||||
if (me.railroad_bits & 0x40) {
|
||||
let current = me.railroad_bits >> 8;
|
||||
for (let i = 0, l = me.type._track_order.length; i < l; i++) {
|
||||
if (me.track_switch !== null) {
|
||||
let current = me.track_switch;
|
||||
for (let i = 0, l = me.type.track_order.length; i < l; i++) {
|
||||
current = (current + 1) % l;
|
||||
if (me.railroad_bits & (1 << current))
|
||||
if (me.tracks & (1 << current))
|
||||
break;
|
||||
}
|
||||
level._set_tile_prop(me, 'railroad_bits', (current << 8) | (me.railroad_bits & 0xff));
|
||||
level._set_tile_prop(me, 'track_switch', current);
|
||||
}
|
||||
},
|
||||
has_opening(me, direction) {
|
||||
@ -1817,6 +1817,9 @@ const TILE_TYPES = {
|
||||
draw_layer: DRAW_LAYERS.terrain,
|
||||
is_hint: true,
|
||||
blocks_collision: COLLISION.block_cc1 | COLLISION.monster_solid,
|
||||
populate_defaults(me) {
|
||||
me.hint_text = null; // optional, may use level's hint instead
|
||||
},
|
||||
},
|
||||
socket: {
|
||||
draw_layer: DRAW_LAYERS.terrain,
|
||||
|
||||
38
style.css
38
style.css
@ -1077,7 +1077,16 @@ form.editor-popup-tile-editor {
|
||||
form.editor-popup-tile-editor.--above {
|
||||
margin-top: -1em;
|
||||
}
|
||||
.popup-chevron {
|
||||
form.editor-popup-tile-editor h3 {
|
||||
border-bottom: 1px dotted #606060;
|
||||
}
|
||||
form.editor-popup-tile-editor * + h3 {
|
||||
margin-top: 0.25em;
|
||||
}
|
||||
/* Use ::before for a chevron pointing at the tile in question */
|
||||
form.editor-popup-tile-editor::before {
|
||||
content: '';
|
||||
display: block;
|
||||
position: absolute;
|
||||
border: 1em solid transparent;
|
||||
left: var(--chevron-offset);
|
||||
@ -1088,7 +1097,7 @@ form.editor-popup-tile-editor.--above {
|
||||
border-bottom-color: white;
|
||||
filter: drop-shadow(0 -1px 0 black);
|
||||
}
|
||||
form.editor-popup-tile-editor.--above .popup-chevron {
|
||||
form.editor-popup-tile-editor.--above::before {
|
||||
top: auto;
|
||||
bottom: -1em;
|
||||
border: 1em solid transparent;
|
||||
@ -1100,7 +1109,7 @@ form.editor-popup-tile-editor.--above .popup-chevron {
|
||||
* characters are preceded by radio buttons, which we hide for simplicity */
|
||||
ol.editor-letter-tile-picker {
|
||||
display: grid;
|
||||
grid: auto-flow 1.5em / repeat(16, 1.5em);
|
||||
grid: auto-flow 1.5em / repeat(17, 1.5em);
|
||||
text-align: center;
|
||||
font-family: monospace;
|
||||
}
|
||||
@ -1123,5 +1132,28 @@ textarea.editor-hint-tile-text {
|
||||
height: 20vh;
|
||||
min-width: 15rem;
|
||||
min-height: 5rem;
|
||||
border: none;
|
||||
font-family: serif;
|
||||
}
|
||||
/* Railroad tracks are... complicated */
|
||||
ul.editor-railroad-tile-tracks {
|
||||
display: grid;
|
||||
grid: auto-flow 3em / repeat(3, 3em);
|
||||
gap: 0.25em;
|
||||
}
|
||||
ul.editor-railroad-tile-tracks input {
|
||||
display: none;
|
||||
}
|
||||
ul.editor-railroad-tile-tracks svg {
|
||||
display: block;
|
||||
width: 3em;
|
||||
fill: none;
|
||||
stroke: #c0c0c0;
|
||||
stroke-width: 2;
|
||||
}
|
||||
ul.editor-railroad-tile-tracks input:checked + svg {
|
||||
stroke: hsl(225, 90%, 50%);
|
||||
}
|
||||
ul.editor-railroad-tile-tracks.--switch input:checked + svg {
|
||||
stroke: hsl(15, 90%, 50%);
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user