From abbda898c75f28256ffa1a351671a910f43478e8 Mon Sep 17 00:00:00 2001 From: "Eevee (Evelyn Woods)" Date: Sun, 21 Apr 2024 03:53:57 -0600 Subject: [PATCH] Add support for gray buttons to the adjust tool --- js/editor/mouseops.js | 112 +++++++++++++++++++++++++++++++++++++++++- js/tiletypes.js | 71 +++++++++++++------------- style.css | 8 +++ 3 files changed, 153 insertions(+), 38 deletions(-) diff --git a/js/editor/mouseops.js b/js/editor/mouseops.js index 9f9f336..4d6ba3c 100644 --- a/js/editor/mouseops.js +++ b/js/editor/mouseops.js @@ -1201,6 +1201,42 @@ const ADJUST_TOGGLES_CCW = {}; } } } +// Little wrapper that allows calling (simple) callbacks on tile types from the editor. +// Note that editor tiles don't even have .cell, so we have to fake that too +class DummyRunningLevel { + constructor(editor, tile, cell) { + this.editor = editor; + this.tile = tile; + this.cell = cell; + } + + _set_tile_prop(tile, key, value) { + if (tile !== this.tile) { + console.error("DummyRunningLevel._set_tile_prop called on an unknown tile:", tile, "expected:", this.tile); + return; + } + + this.editor.place_in_cell(this.cell, {...tile, key: value}); + } + + transmute_tile(tile, type_name) { + if (tile !== this.tile) { + console.error("DummyRunningLevel.transmute_tile called on an unknown tile:", tile, "expected:", this.tile); + return; + } + let type = TILE_TYPES[type_name]; + if (! type) { + console.error("DummyRunningLevel.transmute_tile called with bad type:", type_name); + return; + } + if (tile.type.layer !== type.layer) { + console.error("DummyRunningLevel.transmute_tile refusing to change tile layers:", tile, type_name); + return; + } + + this.editor.place_in_cell(this.cell, {...tile, type}); + } +} // Tiles with special behavior when clicked const ADJUST_SPECIAL = { button_green(editor) { @@ -1221,7 +1257,24 @@ const ADJUST_SPECIAL = { } } }, + button_gray(editor, tile, cell) { + // Toggle gray objects... er... objects affected by gray buttons + for (let dy = -2; dy <= 2; dy++) { + for (let dx = -2; dx <= 2; dx++) { + if (dx === 0 && dy === 0) + continue; + let other_cell = editor.cell(cell.x + dx, cell.y + dy); + if (! other_cell) + continue; + for (let other of other_cell) { + if (other && other.type.on_gray_button && other.type.is_gray_button_editor_safe) { + other.type.on_gray_button(other, new DummyRunningLevel(editor, other, other_cell)); + } + } + } + } + }, }; // TODO maybe better visual feedback of what will happen when you click? // - rotate terrain (cw, ccw) @@ -1229,6 +1282,59 @@ const ADJUST_SPECIAL = { // - rotate actor (cw, ccw) // - press button export class AdjustOperation extends MouseOperation { + constructor(...args) { + super(...args); + + this.gray_button_preview = mk_svg('g.overlay-transient', {'data-source': 'AdjustOperation'}); + this.gray_button_preview.append(mk_svg('rect.overlay-adjust-gray-button-radius', { + x: -2, + y: -2, + width: 5, + height: 5, + })); + this.editor.svg_overlay.append(this.gray_button_preview); + + } + + handle_hover(client_x, client_y, frac_cell_x, frac_cell_y, cell_x, cell_y) { + let cell = this.cell(cell_x, cell_y); + let terrain = cell[LAYERS.terrain]; + if (terrain.type.name === 'button_gray') { + this.gray_button_preview.classList.add('--visible'); + this.gray_button_preview.setAttribute('transform', `translate(${cell_x} ${cell_y})`); + for (let el of this.gray_button_preview.querySelectorAll('rect.overlay-adjust-gray-button-shroud')) { + el.remove(); + } + // The easiest way I can find to preview this is to slap an overlay on everything NOT + // affected by the button. Try to consolidate some of the resulting rectangles though + for (let dy = -2; dy <= 2; dy++) { + let last_rect, last_dx; + for (let dx = -2; dx <= 2; dx++) { + let target = this.cell(cell_x + dx, cell_y + dy); + if (target && target !== cell && target[LAYERS.terrain].type.on_gray_button) + continue; + + if (last_rect && last_dx === dx - 1) { + last_rect.setAttribute('width', 1 + parseInt(last_rect.getAttribute('width'), 10)); + } + else { + last_rect = mk_svg('rect.overlay-adjust-gray-button-shroud', { + x: dx, + y: dy, + width: 1, + height: 1, + }); + this.gray_button_preview.append(last_rect); + } + last_dx = dx; + } + } + } + else { + this.gray_button_preview.classList.remove('--visible'); + } + } + handle_press() { let cell = this.cell(this.prev_cell_x, this.prev_cell_y); if (this.ctrl) { @@ -1274,7 +1380,7 @@ export class AdjustOperation extends MouseOperation { // Other special tile behavior let special = ADJUST_SPECIAL[tile.type.name]; if (special) { - special(this.editor); + special(this.editor, tile, cell); this.editor.commit_undo(); break; } @@ -1283,6 +1389,10 @@ export class AdjustOperation extends MouseOperation { // Adjust tool doesn't support dragging // TODO should it? // TODO if it does then it should end as soon as you spawn a popup + do_destroy() { + this.gray_button_preview.remove(); + super.do_destroy(); + } } // FIXME currently allows creating outside the map bounds and moving beyond the right/bottom, sigh diff --git a/js/tiletypes.js b/js/tiletypes.js index fcf70c1..d18d061 100644 --- a/js/tiletypes.js +++ b/js/tiletypes.js @@ -66,6 +66,7 @@ function _define_force_floor(direction, opposite_type) { activate(me, level) { level.transmute_tile(me, opposite_type); }, + is_gray_button_editor_safe: true, on_gray_button: activate_me, on_power: activate_me, }; @@ -505,6 +506,7 @@ const TILE_TYPES = { activate(me, level) { level.transmute_tile(me, 'swivel_se'); }, + is_gray_button_editor_safe: true, on_gray_button: activate_me, on_power: activate_me, }, @@ -522,6 +524,7 @@ const TILE_TYPES = { activate(me, level) { level.transmute_tile(me, 'swivel_sw'); }, + is_gray_button_editor_safe: true, on_gray_button: activate_me, on_power: activate_me, }, @@ -539,6 +542,7 @@ const TILE_TYPES = { activate(me, level) { level.transmute_tile(me, 'swivel_nw'); }, + is_gray_button_editor_safe: true, on_gray_button: activate_me, on_power: activate_me, }, @@ -556,6 +560,7 @@ const TILE_TYPES = { activate(me, level) { level.transmute_tile(me, 'swivel_ne'); }, + is_gray_button_editor_safe: true, on_gray_button: activate_me, on_power: activate_me, }, @@ -653,6 +658,7 @@ const TILE_TYPES = { on_power(me, level) { me.type._switch_track(me, level); }, + is_gray_button_editor_safe: true, on_gray_button(me, level) { me.type._switch_track(me, level); }, @@ -791,6 +797,7 @@ const TILE_TYPES = { activate(me, level) { level.transmute_tile(me, 'turntable_ccw'); }, + is_gray_button_editor_safe: true, on_gray_button: activate_me, on_power: activate_me, }, @@ -812,6 +819,7 @@ const TILE_TYPES = { activate(me, level) { level.transmute_tile(me, 'turntable_cw'); }, + is_gray_button_editor_safe: true, on_gray_button: activate_me, on_power: activate_me, }, @@ -1328,12 +1336,12 @@ const TILE_TYPES = { level.pending_green_toggle && (other.type.collision_mask & COLLISION.all_but_ghost)); }, - on_gray_button(me, level) { + activate(me, level) { level.transmute_tile(me, 'green_wall'); }, - on_power(me, level) { - me.type.on_gray_button(me, level); - }, + is_gray_button_editor_safe: true, + on_gray_button: activate_me, + on_power: activate_me, }, green_wall: { layer: LAYERS.terrain, @@ -1344,12 +1352,12 @@ const TILE_TYPES = { ! level.pending_green_toggle && (other.type.collision_mask & COLLISION.all_but_ghost)); }, - on_gray_button(me, level) { + activate(me, level) { level.transmute_tile(me, 'green_floor'); }, - on_power(me, level) { - me.type.on_gray_button(me, level); - }, + is_gray_button_editor_safe: true, + on_gray_button: activate_me, + on_power: activate_me, }, green_chip: { layer: LAYERS.item, @@ -1377,28 +1385,24 @@ const TILE_TYPES = { }, purple_floor: { layer: LAYERS.terrain, - on_gray_button(me, level) { + activate(me, level) { level.transmute_tile(me, 'purple_wall'); }, - on_power(me, level) { - me.type.on_gray_button(me, level); - }, - on_depower(me, level) { - me.type.on_gray_button(me, level); - }, + is_gray_button_editor_safe: true, + on_gray_button: activate_me, + on_power: activate_me, + on_depower: activate_me, }, purple_wall: { layer: LAYERS.terrain, blocks_collision: COLLISION.all_but_ghost, - on_gray_button(me, level) { + activate(me, level) { level.transmute_tile(me, 'purple_floor'); }, - on_power(me, level) { - me.type.on_gray_button(me, level); - }, - on_depower(me, level) { - me.type.on_gray_button(me, level); - }, + is_gray_button_editor_safe: true, + on_gray_button: activate_me, + on_power: activate_me, + on_depower: activate_me, }, // Sokoban blocks, buttons, and walls -- they each come in four colors, the buttons can be @@ -1578,9 +1582,8 @@ const TILE_TYPES = { on_power(me, level) { me.type.activate(me, level, true); }, - on_gray_button(me, level) { - me.type.activate(me, level); - }, + is_gray_button_editor_safe: false, + on_gray_button: activate_me, }, trap: { layer: LAYERS.terrain, @@ -2004,24 +2007,18 @@ const TILE_TYPES = { // Do NOT immediately nuke anything on us, or it'd be impossible to push a block off an // adjacent orange button; this is probably why flame jets kill on tics }, - on_gray_button(me, level) { - me.type.activate(me, level); - }, - on_power(me, level) { - me.type.activate(me, level); - }, + is_gray_button_editor_safe: true, + on_gray_button: activate_me, + on_power: activate_me, }, flame_jet_on: { layer: LAYERS.terrain, activate(me, level) { level.transmute_tile(me, 'flame_jet_off'); }, - on_gray_button(me, level) { - me.type.activate(me, level); - }, - on_power(me, level) { - me.type.activate(me, level); - }, + is_gray_button_editor_safe: true, + on_gray_button: activate_me, + on_power: activate_me, on_stand(me, level, other) { // Note that (dirt?) blocks, fireballs, and anything with fire boots are immune // TODO would be neat if this understood "ignores anything with fire immunity" but that diff --git a/style.css b/style.css index f22ce19..a1f4b6b 100644 --- a/style.css +++ b/style.css @@ -2290,6 +2290,14 @@ svg.level-editor-overlay g.overlay-connection.--implicit line.-arrow { svg.level-editor-overlay g.overlay-connection line.-arrow { marker-end: url(#overlay-arrowhead); } +svg.level-editor-overlay .overlay-adjust-gray-button-radius { + stroke: #f4f4f4; + fill: hsla(10, 10%, 80%, 0.125); +} +svg.level-editor-overlay .overlay-adjust-gray-button-shroud { + stroke: none; + fill: hsla(10, 10%, 60%, 0.75); +} svg.level-editor-overlay rect.overlay-camera { stroke: #808080; fill: #80808040;