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',
|
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)
|
// TODO cc2 order is: swivel, thinwalls, canopy (and yes you can have them all in the same tile)
|
||||||
export const DRAW_LAYERS = {
|
export const DRAW_LAYERS = {
|
||||||
|
|||||||
@ -1,11 +1,10 @@
|
|||||||
import { TransientOverlay } from './main-base.js';
|
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
|
// FIXME could very much stand to have a little animation when appearing
|
||||||
class TileEditorOverlay extends TransientOverlay {
|
class TileEditorOverlay extends TransientOverlay {
|
||||||
constructor(conductor) {
|
constructor(conductor) {
|
||||||
let root = mk('form.editor-popup-tile-editor');
|
let root = mk('form.editor-popup-tile-editor');
|
||||||
root.append(mk('span.popup-chevron'));
|
|
||||||
super(conductor, root);
|
super(conductor, root);
|
||||||
this.editor = conductor.editor;
|
this.editor = conductor.editor;
|
||||||
this.tile = null;
|
this.tile = null;
|
||||||
@ -24,34 +23,41 @@ class LetterTileEditor extends TileEditorOverlay {
|
|||||||
constructor(conductor) {
|
constructor(conductor) {
|
||||||
super(conductor);
|
super(conductor);
|
||||||
|
|
||||||
|
this.root.append(mk('h3', "Letter tile"));
|
||||||
let list = mk('ol.editor-letter-tile-picker');
|
let list = mk('ol.editor-letter-tile-picker');
|
||||||
this.root.append(list);
|
this.root.append(list);
|
||||||
this.glyph_elements = {};
|
this.glyph_elements = {};
|
||||||
for (let c = 32; c < 128; c++) {
|
let add = glyph => {
|
||||||
let glyph = String.fromCharCode(c);
|
|
||||||
let input = mk('input', {type: 'radio', name: 'glyph', value: glyph});
|
let input = mk('input', {type: 'radio', name: 'glyph', value: glyph});
|
||||||
this.glyph_elements[glyph] = input;
|
this.glyph_elements[glyph] = input;
|
||||||
let item = mk('li', mk('label', input, mk('span.-glyph', glyph)));
|
let item = mk('li', mk('label', input, mk('span.-glyph', glyph)));
|
||||||
list.append(item);
|
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 => {
|
list.addEventListener('change', ev => {
|
||||||
let glyph = this.root.elements['glyph'].value;
|
|
||||||
if (this.tile) {
|
if (this.tile) {
|
||||||
this.tile.ascii_code = glyph.charCodeAt(0);
|
this.tile.overlaid_glyph = this.root.elements['glyph'].value;
|
||||||
// FIXME should be able to mark tiles as dirty, also this is sure a mouthful
|
this.editor.mark_tile_dirty(this.tile);
|
||||||
this.conductor.editor.renderer.draw();
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
edit_tile(tile) {
|
edit_tile(tile) {
|
||||||
super.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) {
|
static configure_tile_defaults(tile) {
|
||||||
tile.ascii_code = 32;
|
tile.type.populate_defaults(tile);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -59,29 +65,110 @@ class HintTileEditor extends TileEditorOverlay {
|
|||||||
constructor(conductor) {
|
constructor(conductor) {
|
||||||
super(conductor);
|
super(conductor);
|
||||||
|
|
||||||
|
this.root.append(mk('h3', "Hint text"));
|
||||||
this.text = mk('textarea.editor-hint-tile-text');
|
this.text = mk('textarea.editor-hint-tile-text');
|
||||||
this.root.append(this.text);
|
this.root.append(this.text);
|
||||||
this.text.addEventListener('input', ev => {
|
this.text.addEventListener('input', ev => {
|
||||||
if (this.tile) {
|
if (this.tile) {
|
||||||
this.tile.specific_hint = this.text.value;
|
this.tile.hint_text = this.text.value;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
edit_tile(tile) {
|
edit_tile(tile) {
|
||||||
super.edit_tile(tile);
|
super.edit_tile(tile);
|
||||||
this.text.value = tile.specific_hint ?? "";
|
this.text.value = tile.hint_text ?? "";
|
||||||
}
|
}
|
||||||
|
|
||||||
static configure_tile_defaults(tile) {
|
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 = {
|
export const TILES_WITH_PROPS = {
|
||||||
floor_letter: LetterTileEditor,
|
floor_letter: LetterTileEditor,
|
||||||
hint: HintTileEditor,
|
hint: HintTileEditor,
|
||||||
|
railroad: RailroadTileEditor,
|
||||||
// TODO various wireable tiles
|
// TODO various wireable tiles
|
||||||
// TODO initial value of counter
|
// TODO initial value of counter
|
||||||
// TODO cloner arrows
|
// 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 * as format_base from './format-base.js';
|
||||||
import TILE_TYPES from './tiletypes.js';
|
import TILE_TYPES from './tiletypes.js';
|
||||||
import * as util from './util.js';
|
import * as util from './util.js';
|
||||||
@ -374,11 +374,28 @@ const TILE_ENCODING = {
|
|||||||
modifier: {
|
modifier: {
|
||||||
_parts: ['ne', 'se', 'sw', 'ne', 'ew', 'ns'],
|
_parts: ['ne', 'se', 'sw', 'ne', 'ew', 'ns'],
|
||||||
decode(tile, mask) {
|
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) {
|
encode(tile) {
|
||||||
// TODO
|
let ret = tile.tracks & 0x3f;
|
||||||
return 0;
|
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',
|
name: 'floor_letter',
|
||||||
modifier: {
|
modifier: {
|
||||||
decode(tile, ascii_code) {
|
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) {
|
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;
|
level.hint = str;
|
||||||
}
|
}
|
||||||
else if (type === 'NOTE') {
|
else if (type === 'NOTE') {
|
||||||
// Author's comments... but might also include multiple hints
|
// Author's comments... but might also include multiple hints for levels with
|
||||||
// for levels with multiple hint tiles, delineated by [CLUE].
|
// multiple hint tiles, delineated by [CLUE] (anywhere in the line (!)).
|
||||||
// For my purposes, extra hints are associated with the
|
// LL treats extra hints as tile properties, so store them for later
|
||||||
// individual tiles, so we'll map those later
|
[level.comment, ...extra_hints] = str.split(/\n?^.*\[CLUE\].*$\n?/mg);
|
||||||
[level.comment, ...extra_hints] = str.split(/^\[CLUE\]$/mg);
|
|
||||||
}
|
}
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@ -1018,13 +1049,14 @@ export function parse_level(buf, number = 1) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Connect extra hints
|
// Connect extra hints
|
||||||
let h = 0;
|
for (let [i, tile] of hint_tiles.entries()) {
|
||||||
for (let tile of hint_tiles) {
|
if (i < extra_hints.length) {
|
||||||
if (h > extra_hints.length)
|
tile.hint_text = extra_hints[i];
|
||||||
break;
|
}
|
||||||
|
else {
|
||||||
tile.specific_hint = extra_hints[h];
|
// Fall back to regular hint
|
||||||
h++;
|
tile.hint_text = null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return level;
|
return level;
|
||||||
|
|||||||
@ -297,7 +297,7 @@ export class Level {
|
|||||||
let tile = Tile.from_template(template_tile);
|
let tile = Tile.from_template(template_tile);
|
||||||
if (tile.type.is_hint) {
|
if (tile.type.is_hint) {
|
||||||
// Copy over the tile-specific hint, if any
|
// 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) {
|
if (tile.type.is_power_source) {
|
||||||
@ -390,7 +390,7 @@ export class Level {
|
|||||||
tile.type.on_ready(tile, this);
|
tile.type.on_ready(tile, this);
|
||||||
}
|
}
|
||||||
if (cell === this.player.cell && tile.type.is_hint) {
|
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) {
|
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
|
// Get the corresponding bit
|
||||||
let bit = null;
|
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) ||
|
if ((track[0] === this.entry_direction && track[1] === exit_direction) ||
|
||||||
(track[1] === this.entry_direction && track[0] === 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 cell = this.cell(prevx, prevy);
|
||||||
let terrain = cell[0];
|
let terrain = cell[0];
|
||||||
if (terrain.type.name === 'railroad') {
|
if (terrain.type.name === 'railroad') {
|
||||||
terrain.railroad_bits |= bit;
|
terrain.tracks |= bit;
|
||||||
}
|
}
|
||||||
else {
|
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);
|
this.editor.place_in_cell(prevx, prevy, terrain);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -364,17 +366,19 @@ class AdjustOperation extends MouseOperation {
|
|||||||
for (let tile of cell) {
|
for (let tile of cell) {
|
||||||
// Rotate railroads, which are a bit complicated
|
// Rotate railroads, which are a bit complicated
|
||||||
if (tile.type.name === 'railroad') {
|
if (tile.type.name === 'railroad') {
|
||||||
let new_bits = 0;
|
let new_tracks = 0;
|
||||||
for (let [i, new_bit] of [1, 2, 3, 0, 5, 4].entries()) {
|
let rotated_tracks = [1, 2, 3, 0, 5, 4];
|
||||||
if (tile.railroad_bits & (1 << i)) {
|
for (let [i, new_bit] of rotated_tracks.entries()) {
|
||||||
new_bits |= (1 << new_bit);
|
if (tile.tracks & (1 << i)) {
|
||||||
|
new_tracks |= (1 << new_bit);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (tile.railroad_bits & 0x40) {
|
tile.tracks = new_tracks;
|
||||||
new_bits |= 0x40;
|
|
||||||
|
if (tile.switch_track !== null) {
|
||||||
|
tile.switch_track = rotated_tracks[tile.switch_track];
|
||||||
}
|
}
|
||||||
// TODO high byte also
|
tile.entered_direction = DIRECTIONS[tile.entered_direction].right;
|
||||||
tile.railroad_bits = new_bits;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO also directional blocks
|
// TODO also directional blocks
|
||||||
@ -832,7 +836,6 @@ export class Editor extends PrimaryView {
|
|||||||
this.selected_tile_el.addEventListener('click', ev => {
|
this.selected_tile_el.addEventListener('click', ev => {
|
||||||
if (this.palette_selection && TILES_WITH_PROPS[this.palette_selection.type.name]) {
|
if (this.palette_selection && TILES_WITH_PROPS[this.palette_selection.type.name]) {
|
||||||
// FIXME use tile bounds
|
// FIXME use tile bounds
|
||||||
// FIXME redraw the tile after editing
|
|
||||||
this.open_tile_prop_overlay(this.palette_selection, ev.clientX, ev.clientY);
|
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;
|
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) {
|
if (this.palette_selection) {
|
||||||
let entry = this.palette[this.palette_selection.type.name];
|
let entry = this.palette[this.palette_selection.type.name];
|
||||||
if (entry) {
|
if (entry) {
|
||||||
@ -989,6 +987,8 @@ export class Editor extends PrimaryView {
|
|||||||
this.palette[name].classList.add('--selected');
|
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
|
// Some tools obviously don't work with a palette selection, in which case changing tiles
|
||||||
// should default you back to the pencil
|
// should default you back to the pencil
|
||||||
if (this.current_tool === 'adjust') {
|
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) {
|
is_in_bounds(x, y) {
|
||||||
return 0 <= x && x < this.stored_level.size_x && 0 <= y && y < this.stored_level.size_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
|
// Horizontal position: centered, but kept within the screen
|
||||||
let left;
|
let left;
|
||||||
let margin = 8; // prefer to not quite touch the edges
|
let margin = 8; // prefer to not quite touch the edges
|
||||||
let halfwidth = root.offsetWidth / 2 + margin;
|
if (document.body.clientWidth < root.offsetWidth + margin * 2) {
|
||||||
if (document.body.clientWidth / 2 < halfwidth) {
|
|
||||||
// It doesn't fit on the screen at all, so there's nothing we can do; just center it
|
// 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;
|
left = (document.body.clientWidth - root.offsetWidth) / 2;
|
||||||
}
|
}
|
||||||
else {
|
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.left = `${left}px`;
|
||||||
root.style.setProperty('--chevron-offset', `${x0 - left}px`);
|
root.style.setProperty('--chevron-offset', `${x0 - left}px`);
|
||||||
|
|||||||
@ -63,18 +63,25 @@ export const CC2_TILESET_LAYOUT = {
|
|||||||
wall_invisible: [0, 2],
|
wall_invisible: [0, 2],
|
||||||
wall_appearing: [0, 2],
|
wall_appearing: [0, 2],
|
||||||
wall: [1, 2],
|
wall: [1, 2],
|
||||||
floor_letter: [2, 2],
|
floor_letter: {
|
||||||
'floor_letter#ascii': {
|
special: 'letter',
|
||||||
x0: 0,
|
base: [2, 2],
|
||||||
y0: 0,
|
letter_glyphs: {
|
||||||
width: 16,
|
// Arrows
|
||||||
height: 1,
|
"⬆": [14, 31],
|
||||||
},
|
"➡": [14.5, 31],
|
||||||
'floor_letter#arrows': {
|
"⬇": [15, 31],
|
||||||
north: [14, 31],
|
"⬅": [15.5, 31],
|
||||||
east: [14.5, 31],
|
},
|
||||||
south: [15, 31],
|
letter_ranges: [{
|
||||||
west: [15.5, 31],
|
// ASCII text (only up through uppercase)
|
||||||
|
range: [32, 96],
|
||||||
|
x0: 0,
|
||||||
|
y0: 0,
|
||||||
|
w: 0.5,
|
||||||
|
h: 0.5,
|
||||||
|
columns: 32,
|
||||||
|
}],
|
||||||
},
|
},
|
||||||
thief_tools: [3, 2],
|
thief_tools: [3, 2],
|
||||||
socket: [4, 2],
|
socket: [4, 2],
|
||||||
@ -830,6 +837,31 @@ export class Tileset {
|
|||||||
blit(coords[0], coords[1], ...mask);
|
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) {
|
_draw_logic_gate(drawspec, tile, tic, blit) {
|
||||||
// Layer 1: wiring state
|
// Layer 1: wiring state
|
||||||
// Always draw the unpowered wire base
|
// Always draw the unpowered wire base
|
||||||
@ -876,15 +908,15 @@ export class Tileset {
|
|||||||
let visible_parts = [];
|
let visible_parts = [];
|
||||||
let topmost_part = null;
|
let topmost_part = null;
|
||||||
for (let [i, part] of part_order.entries()) {
|
for (let [i, part] of part_order.entries()) {
|
||||||
if (tile && (tile.railroad_bits & (1 << i))) {
|
if (tile && (tile.tracks & (1 << i))) {
|
||||||
if ((tile.railroad_bits >> 8) === i) {
|
if (tile.track_switch === i) {
|
||||||
topmost_part = part;
|
topmost_part = part;
|
||||||
}
|
}
|
||||||
visible_parts.push(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) {
|
for (let part of visible_parts) {
|
||||||
this._draw_standard(drawspec.railroad_ties[part], tile, tic, blit);
|
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
|
// TODO shift everything to use this style, this is ridiculous
|
||||||
if (drawspec.special) {
|
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);
|
this._draw_logic_gate(drawspec, tile, tic, blit);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -1144,36 +1180,6 @@ export class Tileset {
|
|||||||
}
|
}
|
||||||
blit(x, y, x0, y0, x1 - x0, y1 - y0);
|
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) {
|
_rotate(direction, x0, y0, x1, y1) {
|
||||||
|
|||||||
@ -90,6 +90,9 @@ const TILE_TYPES = {
|
|||||||
},
|
},
|
||||||
floor_letter: {
|
floor_letter: {
|
||||||
draw_layer: DRAW_LAYERS.terrain,
|
draw_layer: DRAW_LAYERS.terrain,
|
||||||
|
populate_defaults(me) {
|
||||||
|
me.overlaid_glyph = "?";
|
||||||
|
},
|
||||||
},
|
},
|
||||||
// TODO possibly this should be a single tile
|
// TODO possibly this should be a single tile
|
||||||
floor_custom_green: {
|
floor_custom_green: {
|
||||||
@ -316,7 +319,7 @@ const TILE_TYPES = {
|
|||||||
// Railroad
|
// Railroad
|
||||||
railroad: {
|
railroad: {
|
||||||
draw_layer: DRAW_LAYERS.terrain,
|
draw_layer: DRAW_LAYERS.terrain,
|
||||||
_track_order: [
|
track_order: [
|
||||||
['north', 'east'],
|
['north', 'east'],
|
||||||
['south', 'east'],
|
['south', 'east'],
|
||||||
['south', 'west'],
|
['south', 'west'],
|
||||||
@ -324,15 +327,12 @@ const TILE_TYPES = {
|
|||||||
['east', 'west'],
|
['east', 'west'],
|
||||||
['north', 'south'],
|
['north', 'south'],
|
||||||
],
|
],
|
||||||
// FIXME railroad_bits sucks, split into some useful variables
|
populate_defaults(me) {
|
||||||
on_ready(me) {
|
me.tracks = 0; // bitmask of bits 0-5, corresponding to track order above
|
||||||
// If there's already an actor on top of us, assume it entered the way it's already
|
me.track_switch = null; // null, or 0-5 indicating the active switched track
|
||||||
// facing (which may be illegal, in which case it can't leave)
|
// If there's already an actor on us, it's treated as though it entered the tile moving
|
||||||
// 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.
|
// in this direction, which is given in the save file and defaults to zero i.e. north
|
||||||
let actor = me.cell.get_actor();
|
me.entered_direction = 'north';
|
||||||
if (actor) {
|
|
||||||
me.entered_direction = actor.direction;
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
// TODO feel like "ignores" was the wrong idea and there should just be some magic flags for
|
// 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
|
// particular objects that can be immune to. or maybe those objects should have their own
|
||||||
@ -345,28 +345,28 @@ const TILE_TYPES = {
|
|||||||
return true;
|
return true;
|
||||||
},
|
},
|
||||||
*_iter_tracks(me) {
|
*_iter_tracks(me) {
|
||||||
let order = me.type._track_order;
|
let order = me.type.track_order;
|
||||||
if (me.railroad_bits & 0x40) {
|
if (me.track_switch !== null) {
|
||||||
// FIXME what happens if the "top" track is not actually a valid track???
|
// 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 {
|
else {
|
||||||
for (let [i, track] of order.entries()) {
|
for (let [i, track] of order.entries()) {
|
||||||
if (me.railroad_bits & (1 << i)) {
|
if (me.tracks & (1 << i)) {
|
||||||
yield track;
|
yield track;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
_switch_track(me, level) {
|
_switch_track(me, level) {
|
||||||
if (me.railroad_bits & 0x40) {
|
if (me.track_switch !== null) {
|
||||||
let current = me.railroad_bits >> 8;
|
let current = me.track_switch;
|
||||||
for (let i = 0, l = me.type._track_order.length; i < l; i++) {
|
for (let i = 0, l = me.type.track_order.length; i < l; i++) {
|
||||||
current = (current + 1) % l;
|
current = (current + 1) % l;
|
||||||
if (me.railroad_bits & (1 << current))
|
if (me.tracks & (1 << current))
|
||||||
break;
|
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) {
|
has_opening(me, direction) {
|
||||||
@ -1817,6 +1817,9 @@ const TILE_TYPES = {
|
|||||||
draw_layer: DRAW_LAYERS.terrain,
|
draw_layer: DRAW_LAYERS.terrain,
|
||||||
is_hint: true,
|
is_hint: true,
|
||||||
blocks_collision: COLLISION.block_cc1 | COLLISION.monster_solid,
|
blocks_collision: COLLISION.block_cc1 | COLLISION.monster_solid,
|
||||||
|
populate_defaults(me) {
|
||||||
|
me.hint_text = null; // optional, may use level's hint instead
|
||||||
|
},
|
||||||
},
|
},
|
||||||
socket: {
|
socket: {
|
||||||
draw_layer: DRAW_LAYERS.terrain,
|
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 {
|
form.editor-popup-tile-editor.--above {
|
||||||
margin-top: -1em;
|
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;
|
position: absolute;
|
||||||
border: 1em solid transparent;
|
border: 1em solid transparent;
|
||||||
left: var(--chevron-offset);
|
left: var(--chevron-offset);
|
||||||
@ -1088,7 +1097,7 @@ form.editor-popup-tile-editor.--above {
|
|||||||
border-bottom-color: white;
|
border-bottom-color: white;
|
||||||
filter: drop-shadow(0 -1px 0 black);
|
filter: drop-shadow(0 -1px 0 black);
|
||||||
}
|
}
|
||||||
form.editor-popup-tile-editor.--above .popup-chevron {
|
form.editor-popup-tile-editor.--above::before {
|
||||||
top: auto;
|
top: auto;
|
||||||
bottom: -1em;
|
bottom: -1em;
|
||||||
border: 1em solid transparent;
|
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 */
|
* characters are preceded by radio buttons, which we hide for simplicity */
|
||||||
ol.editor-letter-tile-picker {
|
ol.editor-letter-tile-picker {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid: auto-flow 1.5em / repeat(16, 1.5em);
|
grid: auto-flow 1.5em / repeat(17, 1.5em);
|
||||||
text-align: center;
|
text-align: center;
|
||||||
font-family: monospace;
|
font-family: monospace;
|
||||||
}
|
}
|
||||||
@ -1123,5 +1132,28 @@ textarea.editor-hint-tile-text {
|
|||||||
height: 20vh;
|
height: 20vh;
|
||||||
min-width: 15rem;
|
min-width: 15rem;
|
||||||
min-height: 5rem;
|
min-height: 5rem;
|
||||||
|
border: none;
|
||||||
font-family: serif;
|
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