Split the adjust tool into rotate/adjust
It was trying to do too many things. Also, the adjust tool is now free to operate on actors, and can toggle the form of a number of them. - Rearranged the palette to put colored tiles in canonical key order, finally - Expanded the size of the SVG overlay slightly so hover effects don't get cut off at the level border - Fixed some MouseOperation nonsense by simply using the same object when the same operation is bound to both mouse buttons - Added a verb and preview to the adjust tool, in the hopes of making it slightly more clear what it might do - Enhanced the adjust tool to place individual thin walls and frame arrows
|
Before Width: | Height: | Size: 235 B After Width: | Height: | Size: 478 B |
|
Before Width: | Height: | Size: 235 B After Width: | Height: | Size: 478 B |
|
Before Width: | Height: | Size: 440 B After Width: | Height: | Size: 506 B |
BIN
icons/tool-rotate.png
Normal file
|
After Width: | Height: | Size: 439 B |
BIN
icons/tool-text.png
Normal file
|
After Width: | Height: | Size: 452 B |
@ -59,10 +59,18 @@ export const TOOLS = {
|
||||
op1: mouseops.TrackOperation,
|
||||
op2: mouseops.TrackOperation,
|
||||
},
|
||||
rotate: {
|
||||
icon: 'icons/tool-rotate.png',
|
||||
name: "Rotate",
|
||||
desc: "Rotate existing tiles.\nAffects the top-most tile by default.\n\n[mouse1] Rotate clockwise\n[mouse2] Rotate counter-clockwise\n[ctrl] Target terrain\n[shift] Target actor", // TODO? \n[ctrl] [shift] Affect actor without rotating
|
||||
op1: mouseops.RotateOperation,
|
||||
op2: mouseops.RotateOperation,
|
||||
shortcut: 'r',
|
||||
},
|
||||
adjust: {
|
||||
icon: 'icons/tool-adjust.png',
|
||||
name: "Adjust",
|
||||
desc: "Inspect and edit existing tiles in a variety of ways. Give it a try!\n\n[mouse1] Rotate actor\n[mouse1] Rotate or change terrain\n[mouse1] Press button\n[mouse2] Rotate/toggle in the other direction\n[shift] Always target terrain\n\n[ctrl] [mouse1] Edit properties of complex tiles\n(wires, railroads, hints, etc.)",
|
||||
desc: "Inspect and alter miscellaneous tiles in a variety of ways.\nGive it a try! Affects the top-most tile by default.\n\n[mouse1] Toggle tile type\n[mouse1] Press button\n[mouse2] Edit properties of complex tiles\n(wires, railroads, hints, etc.)\n[ctrl] Target terrain\n[shift] Target actor\n[ctrl] [shift] Target item",
|
||||
op1: mouseops.AdjustOperation,
|
||||
op2: mouseops.AdjustOperation,
|
||||
shortcut: 'a',
|
||||
@ -102,7 +110,7 @@ export const TOOLS = {
|
||||
// slade when you have some selected?
|
||||
// TODO ah, railroads...
|
||||
};
|
||||
export const TOOL_ORDER = ['pencil', 'select_box', 'fill', 'adjust', 'force-floors', 'tracks', 'connect', 'wire', 'camera'];
|
||||
export const TOOL_ORDER = ['pencil', 'select_box', 'fill', 'rotate', 'adjust', 'force-floors', 'tracks', 'connect', 'wire', 'camera'];
|
||||
export const TOOL_SHORTCUTS = {};
|
||||
for (let [tool, tooldef] of Object.entries(TOOLS)) {
|
||||
if (tooldef.shortcut) {
|
||||
@ -139,10 +147,10 @@ export const PALETTE = [{
|
||||
'no_player1_sign',
|
||||
'no_player2_sign',
|
||||
|
||||
'floor_custom_green', 'floor_custom_pink', 'floor_custom_yellow', 'floor_custom_blue',
|
||||
'wall_custom_green', 'wall_custom_pink', 'wall_custom_yellow', 'wall_custom_blue',
|
||||
'floor_custom_pink', 'floor_custom_blue', 'floor_custom_yellow', 'floor_custom_green',
|
||||
'wall_custom_pink', 'wall_custom_blue', 'wall_custom_yellow', 'wall_custom_green',
|
||||
|
||||
'door_blue', 'door_red', 'door_yellow', 'door_green',
|
||||
'door_red', 'door_blue', 'door_yellow', 'door_green',
|
||||
'swivel_nw',
|
||||
'railroad/straight',
|
||||
'railroad/curve',
|
||||
@ -156,8 +164,8 @@ export const PALETTE = [{
|
||||
}, {
|
||||
title: "Items",
|
||||
tiles: [
|
||||
'key_blue', 'key_red', 'key_yellow', 'key_green',
|
||||
'flippers', 'fire_boots', 'cleats', 'suction_boots',
|
||||
'key_red', 'key_blue', 'key_yellow', 'key_green',
|
||||
'cleats', 'suction_boots', 'fire_boots', 'flippers',
|
||||
'hiking_boots', 'speed_boots', 'lightning_bolt', 'railroad_sign',
|
||||
'helmet', 'foil', 'hook', 'xray_eye',
|
||||
'bribe', 'bowling_ball', 'dynamite', 'no_sign',
|
||||
@ -210,10 +218,10 @@ export const PALETTE = [{
|
||||
'button_orange', 'flame_jet_off', 'flame_jet_on',
|
||||
'transmogrifier',
|
||||
|
||||
'teleport_blue',
|
||||
'teleport_red',
|
||||
'teleport_green',
|
||||
'teleport_blue',
|
||||
'teleport_yellow',
|
||||
'teleport_green',
|
||||
'stopwatch_bonus',
|
||||
'stopwatch_penalty',
|
||||
'stopwatch_toggle',
|
||||
@ -248,17 +256,17 @@ export const PALETTE = [{
|
||||
tiles: [
|
||||
'sokoban_block/red',
|
||||
'sokoban_block/blue',
|
||||
'sokoban_block/green',
|
||||
'sokoban_block/yellow',
|
||||
'sokoban_block/green',
|
||||
'sokoban_button/red',
|
||||
'sokoban_button/blue',
|
||||
'sokoban_button/green',
|
||||
'sokoban_button/yellow',
|
||||
'sokoban_button/green',
|
||||
|
||||
'sokoban_wall/red',
|
||||
'sokoban_wall/blue',
|
||||
'sokoban_wall/green',
|
||||
'sokoban_wall/yellow',
|
||||
'sokoban_wall/green',
|
||||
'gate_red',
|
||||
'gate_blue',
|
||||
'gate_yellow',
|
||||
|
||||
@ -80,11 +80,12 @@ export class Editor extends PrimaryView {
|
||||
this.renderer = new CanvasRenderer(this.conductor.tilesets['ll'], 32);
|
||||
this.renderer.perception = 'editor';
|
||||
this.renderer.show_facing = true;
|
||||
this.renderer.canvas.classList.add('editor-renderer-canvas');
|
||||
|
||||
// FIXME need this in load_level which is called even if we haven't been setup yet
|
||||
this.connections_g = mk_svg('g', {'data-name': 'connections'});
|
||||
// This SVG draws vectors on top of the editor, like monster paths and button connections
|
||||
this.svg_overlay = mk_svg('svg.level-editor-overlay', {viewBox: '0 0 32 32'},
|
||||
this.svg_overlay = mk_svg('svg.level-editor-overlay', {viewBox: '-1 -1 34 34'},
|
||||
mk_svg('defs',
|
||||
mk_svg('marker', {id: 'overlay-arrowhead', markerWidth: 4, markerHeight: 4, refX: 3, refY: 2, orient: 'auto'},
|
||||
mk_svg('polygon', {points: '0 0, 4 2, 0 4'}),
|
||||
@ -306,6 +307,8 @@ export class Editor extends PrimaryView {
|
||||
ev.stopPropagation();
|
||||
ev.preventDefault();
|
||||
|
||||
// TODO Alt: Scroll through palette
|
||||
|
||||
let index = ZOOM_LEVELS.findIndex(el => el >= this.zoom);
|
||||
if (index < 0) {
|
||||
index = ZOOM_LEVELS.length - 1;
|
||||
@ -1082,6 +1085,9 @@ export class Editor extends PrimaryView {
|
||||
// Load *implicit* connections
|
||||
this.recreate_implicit_connections();
|
||||
|
||||
// Trace out circuitry
|
||||
this.update_circuits();
|
||||
|
||||
this.renderer.set_level(stored_level);
|
||||
if (this.active) {
|
||||
this.redraw_entire_level();
|
||||
@ -1103,7 +1109,9 @@ export class Editor extends PrimaryView {
|
||||
|
||||
update_viewport_size() {
|
||||
this.renderer.set_viewport_size(this.stored_level.size_x, this.stored_level.size_y);
|
||||
this.svg_overlay.setAttribute('viewBox', `0 0 ${this.stored_level.size_x} ${this.stored_level.size_y}`);
|
||||
this.svg_overlay.setAttribute('viewBox', `-1 -1 ${this.stored_level.size_x + 2} ${this.stored_level.size_y + 2}`);
|
||||
this.svg_overlay.style.setProperty('--tile-width', `${this.renderer.tileset.size_x}px`);
|
||||
this.svg_overlay.style.setProperty('--tile-height', `${this.renderer.tileset.size_y}px`);
|
||||
}
|
||||
|
||||
update_after_size_change() {
|
||||
@ -1188,26 +1196,38 @@ export class Editor extends PrimaryView {
|
||||
this.tool_button_els[this.current_tool].classList.add('-selected');
|
||||
|
||||
// Left button: activate tool
|
||||
this._init_mouse_op(0, this.current_tool && TOOLS[this.current_tool].op1);
|
||||
// Right button: activate tool's alt mode
|
||||
this._init_mouse_op(2, this.current_tool && TOOLS[this.current_tool].op2);
|
||||
let op_type1 = this.current_tool && TOOLS[this.current_tool].op1;
|
||||
let op_type2 = this.current_tool && TOOLS[this.current_tool].op2;
|
||||
// Destroy the old operations. Be careful since they might be the same object
|
||||
if (this.mouse_ops[0]) {
|
||||
this.mouse_ops[0].do_destroy();
|
||||
}
|
||||
if (this.mouse_ops[2] && this.mouse_ops[2] !== this.mouse_ops[0]) {
|
||||
this.mouse_ops[2].do_destroy();
|
||||
}
|
||||
// Create new ones
|
||||
if (op_type1) {
|
||||
this.mouse_ops[0] = new op_type1(this);
|
||||
}
|
||||
else {
|
||||
this.mouse_ops[0] = null;
|
||||
}
|
||||
if (op_type2) {
|
||||
if (op_type1 === op_type2) {
|
||||
// Use the same operation for both buttons, to simplify handling of hovering
|
||||
this.mouse_ops[2] = this.mouse_ops[0];
|
||||
}
|
||||
else {
|
||||
this.mouse_ops[2] = new op_type2(this);
|
||||
}
|
||||
}
|
||||
else {
|
||||
this.mouse_ops[2] = null;
|
||||
}
|
||||
|
||||
this.set_mouse_button(0);
|
||||
}
|
||||
_init_mouse_op(button, op_type) {
|
||||
if (this.mouse_ops[button] && op_type && this.mouse_ops[button] instanceof op_type)
|
||||
// Don't recreate the same type of mouse operation
|
||||
return;
|
||||
|
||||
if (this.mouse_ops[button]) {
|
||||
this.mouse_ops[button].do_destroy();
|
||||
this.mouse_ops[button] = null;
|
||||
}
|
||||
|
||||
if (op_type) {
|
||||
this.mouse_ops[button] = new op_type(this, button);
|
||||
}
|
||||
}
|
||||
|
||||
set_mouse_button(button) {
|
||||
this.mouse_op = this.mouse_ops[button];
|
||||
@ -1396,7 +1416,7 @@ export class Editor extends PrimaryView {
|
||||
ctx.clearRect(0, 0, this.fg_tile_el.width, this.fg_tile_el.height);
|
||||
this.renderer.draw_single_tile_type(
|
||||
this.fg_tile.type.name, this.fg_tile, this.fg_tile_el);
|
||||
for (let mouse_op of this.mouse_ops) {
|
||||
for (let mouse_op of new Set(this.mouse_ops)) {
|
||||
if (mouse_op) {
|
||||
mouse_op.handle_tile_updated();
|
||||
}
|
||||
@ -1408,7 +1428,7 @@ export class Editor extends PrimaryView {
|
||||
ctx.clearRect(0, 0, this.bg_tile_el.width, this.bg_tile_el.height);
|
||||
this.renderer.draw_single_tile_type(
|
||||
this.bg_tile.type.name, this.bg_tile, this.bg_tile_el);
|
||||
for (let mouse_op of this.mouse_ops) {
|
||||
for (let mouse_op of new Set(this.mouse_ops)) {
|
||||
if (mouse_op) {
|
||||
mouse_op.handle_tile_updated(true);
|
||||
}
|
||||
@ -1871,6 +1891,7 @@ export class Editor extends PrimaryView {
|
||||
}
|
||||
}
|
||||
|
||||
// TODO handle old_tile or new_tile being null (won't connect anyway)
|
||||
// TODO explicit connection stuff left:
|
||||
// - adding an explicit connection should delete all the implicit ones from the source
|
||||
// - deleting an explicit connection should add an auto implicit connection
|
||||
@ -1882,6 +1903,7 @@ export class Editor extends PrimaryView {
|
||||
// - if only src, copy original dest
|
||||
// - if only dest, then stamping should only do it if it doesn't already exist?
|
||||
// also arrow should follow the selection
|
||||
// TODO all this stuff needs to apply to transforms as well, oopsie
|
||||
_update_connections(cell, old_tile, new_tile) {
|
||||
if (! (old_tile && ! this.connectable_types.has(old_tile.type.name)) &&
|
||||
! (new_tile && ! this.connectable_types.has(new_tile.type.name)))
|
||||
@ -1889,7 +1911,7 @@ export class Editor extends PrimaryView {
|
||||
// Nothing to do
|
||||
return;
|
||||
}
|
||||
if (old_tile.type.name === new_tile.type.name)
|
||||
if (old_tile && new_tile && old_tile.type.name === new_tile.type.name)
|
||||
return;
|
||||
|
||||
// TODO actually this should also update explicit ones, if the source/dest types are changed
|
||||
@ -1898,11 +1920,11 @@ export class Editor extends PrimaryView {
|
||||
let n = this.cell_to_scalar(cell);
|
||||
|
||||
// Remove an old outgoing connection
|
||||
if (old_tile.type.connects_to) {
|
||||
if (old_tile && old_tile.type.connects_to) {
|
||||
this.__delete_implicit_connection(n);
|
||||
}
|
||||
// Remove an old incoming connection
|
||||
if (old_tile.type.connects_from) {
|
||||
if (old_tile && old_tile.type.connects_from) {
|
||||
let sources = this.reverse_implicit_connections.get(n);
|
||||
if (sources) {
|
||||
// All the buttons pointing at us are now dangling. We could be a little clever
|
||||
@ -1917,11 +1939,11 @@ export class Editor extends PrimaryView {
|
||||
}
|
||||
|
||||
// Add a new outgoing connection
|
||||
if (new_tile.type.connects_to) {
|
||||
if (new_tile && new_tile.type.connects_to) {
|
||||
this._implicit_connect_tile(new_tile, cell, n);
|
||||
}
|
||||
// Add a new incoming connection, which is a bit more complicated
|
||||
if (new_tile.type.connects_from) {
|
||||
if (new_tile && new_tile.type.connects_from) {
|
||||
for (let source_type_name of new_tile.type.connects_from) {
|
||||
let source_type = TILE_TYPES[source_type_name];
|
||||
// For a trap or cloner, we can search backwards until we see another trap or
|
||||
@ -1944,7 +1966,7 @@ export class Editor extends PrimaryView {
|
||||
// every orange button in the level!
|
||||
else if (source_type.connect_order === 'diamond') {
|
||||
for (let source_cell of this.stored_level.linear_cells) {
|
||||
let terrain = source_cell.get_terrain();
|
||||
let terrain = source_cell[LAYERS.terrain];
|
||||
if (terrain.type !== source_type)
|
||||
continue;
|
||||
|
||||
@ -1990,6 +2012,9 @@ export class Editor extends PrimaryView {
|
||||
}
|
||||
}
|
||||
|
||||
update_circuits() {
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------------------------------------
|
||||
// Undo/redo
|
||||
|
||||
|
||||
@ -5,6 +5,7 @@ import { DIRECTIONS, LAYERS } from '../defs.js';
|
||||
import TILE_TYPES from '../tiletypes.js';
|
||||
import { mk, mk_svg, walk_grid } from '../util.js';
|
||||
|
||||
import { SPECIAL_TILE_BEHAVIOR } from './editordefs.js';
|
||||
import { SVGConnection } from './helpers.js';
|
||||
import { TILES_WITH_PROPS } from './tile-overlays.js';
|
||||
|
||||
@ -20,11 +21,10 @@ import { TILES_WITH_PROPS } from './tile-overlays.js';
|
||||
// - set trap as initially open? feels like a weird hack. but it does appear in cc2lp1
|
||||
const MOUSE_BUTTON_MASKS = [1, 4, 2]; // MouseEvent.button/buttons are ordered differently
|
||||
export class MouseOperation {
|
||||
constructor(editor, physical_button) {
|
||||
constructor(editor) {
|
||||
this.editor = editor;
|
||||
this.is_held = false;
|
||||
this.physical_button = physical_button;
|
||||
this.alt_mode = physical_button !== 0;
|
||||
this.held_button = null;
|
||||
this.alt_mode = false;
|
||||
this.ctrl = false;
|
||||
this.shift = false;
|
||||
|
||||
@ -81,8 +81,30 @@ export class MouseOperation {
|
||||
return this.editor.cell(Math.floor(x), Math.floor(y));
|
||||
}
|
||||
|
||||
get_tile_edge() {
|
||||
let frac_x = this.prev_frac_cell_x - this.prev_cell_x;
|
||||
let frac_y = this.prev_frac_cell_y - this.prev_cell_y;
|
||||
if (frac_x >= frac_y) {
|
||||
if (frac_x >= 1 - frac_y) {
|
||||
return 'east';
|
||||
}
|
||||
else {
|
||||
return 'north';
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (frac_x <= 1 - frac_y) {
|
||||
return 'west';
|
||||
}
|
||||
else {
|
||||
return 'south';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
do_press(ev) {
|
||||
this.is_held = true;
|
||||
this.held_button = ev.button;
|
||||
this.alt_mode = (ev.button === 2);
|
||||
this._update_modifiers(ev);
|
||||
|
||||
this.client_x = ev.clientX;
|
||||
@ -107,7 +129,7 @@ export class MouseOperation {
|
||||
let cell_x = Math.floor(frac_cell_x);
|
||||
let cell_y = Math.floor(frac_cell_y);
|
||||
|
||||
if (this.is_held && (ev.buttons & MOUSE_BUTTON_MASKS[this.physical_button]) === 0) {
|
||||
if (this.held_button !== null && (ev.buttons & MOUSE_BUTTON_MASKS[this.held_button]) === 0) {
|
||||
this.do_abort();
|
||||
}
|
||||
|
||||
@ -115,7 +137,7 @@ export class MouseOperation {
|
||||
this.cursor_element.setAttribute('transform', `translate(${cell_x} ${cell_y})`);
|
||||
}
|
||||
|
||||
if (this.is_held) {
|
||||
if (this.held_button !== null) {
|
||||
// Continue a drag even if the mouse goes outside the viewport
|
||||
this.handle_drag(ev.clientX, ev.clientY, frac_cell_x, frac_cell_y, cell_x, cell_y);
|
||||
}
|
||||
@ -191,21 +213,23 @@ export class MouseOperation {
|
||||
}
|
||||
|
||||
do_commit() {
|
||||
if (! this.is_held)
|
||||
if (this.held_button === null)
|
||||
return;
|
||||
|
||||
this.commit_press();
|
||||
this.cleanup_press();
|
||||
this.is_held = false;
|
||||
this.alt_mode = false;
|
||||
this.held_button = null;
|
||||
}
|
||||
|
||||
do_abort() {
|
||||
if (! this.is_held)
|
||||
if (this.held_button === null)
|
||||
return;
|
||||
|
||||
this.abort_press();
|
||||
this.cleanup_press();
|
||||
this.is_held = false;
|
||||
this.alt_mode = false;
|
||||
this.held_button = null;
|
||||
}
|
||||
|
||||
do_destroy() {
|
||||
@ -568,6 +592,7 @@ export class FillOperation extends MouseOperation {
|
||||
// TODO also, delete? there's no delete??
|
||||
// FIXME don't show the overlay text until has_moved
|
||||
// TODO cursor: 'cell' by default...?
|
||||
// FIXME possible to start dragging from outside the level bounds, augh
|
||||
export class SelectOperation extends MouseOperation {
|
||||
handle_press() {
|
||||
if (this.shift) {
|
||||
@ -631,7 +656,11 @@ export class SelectOperation extends MouseOperation {
|
||||
}
|
||||
|
||||
update_pending_selection() {
|
||||
this.pending_selection.set_extrema(this.click_cell_x, this.click_cell_y, this.prev_cell_x, this.prev_cell_y);
|
||||
this.pending_selection.set_extrema(
|
||||
Math.max(0, Math.min(this.editor.stored_level.size_x - 1, this.click_cell_x)),
|
||||
Math.max(0, Math.min(this.editor.stored_level.size_y - 1, this.click_cell_y)),
|
||||
Math.max(0, Math.min(this.editor.stored_level.size_x - 1, this.prev_cell_x)),
|
||||
Math.max(0, Math.min(this.editor.stored_level.size_y - 1, this.prev_cell_y)));
|
||||
}
|
||||
|
||||
commit_press() {
|
||||
@ -1164,40 +1193,189 @@ export class WireOperation extends MouseOperation {
|
||||
}
|
||||
}
|
||||
|
||||
// TODO hmm there's no way to rotate the wires on a circuit block without rotating the block itself
|
||||
// TODO this highlights blocks even though they don't usually show their direction...
|
||||
// maybe put a pencil-like preview tile on here that highlights the tile being targeted, and also
|
||||
// forces showing the arrow on blocks?
|
||||
export class RotateOperation extends MouseOperation {
|
||||
constructor(...args) {
|
||||
super(...args);
|
||||
this.hovered_layer = null;
|
||||
|
||||
this.set_cursor_element(mk_svg('circle.overlay-transient.overlay-adjust-cursor', {
|
||||
cx: 0.5,
|
||||
cy: 0.5,
|
||||
r: 0.75,
|
||||
}));
|
||||
}
|
||||
|
||||
_find_target_tile(cell) {
|
||||
let top_layer = LAYERS.MAX - 1;
|
||||
let bottom_layer = 0;
|
||||
if (this.ctrl) {
|
||||
// ctrl: explicitly target terrain
|
||||
top_layer = LAYERS.terrain;
|
||||
bottom_layer = LAYERS.terrain;
|
||||
}
|
||||
else if (this.shift) {
|
||||
// shift: explicitly target actor
|
||||
top_layer = LAYERS.actor;
|
||||
bottom_layer = LAYERS.actor;
|
||||
}
|
||||
for (let layer = top_layer; layer >= bottom_layer; layer--) {
|
||||
let tile = cell[layer];
|
||||
if (! tile)
|
||||
continue;
|
||||
|
||||
// Detecting if a tile is rotatable is, uhh, a little, complicated
|
||||
if (tile.type.is_actor) {
|
||||
return layer;
|
||||
}
|
||||
// The counter doesn't actually rotate
|
||||
if (tile.type.name === 'logic_gate' && tile.gate_type === 'counter') {
|
||||
continue;
|
||||
}
|
||||
let behavior = SPECIAL_TILE_BEHAVIOR[tile.type.name];
|
||||
if (behavior && behavior.rotate_left) {
|
||||
return layer;
|
||||
}
|
||||
|
||||
if (tile.wire_directions || tile.wire_tunnel_directions) {
|
||||
return layer;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
handle_hover(client_x, client_y, frac_cell_x, frac_cell_y, cell_x, cell_y) {
|
||||
// TODO hrmm if we undo without moving the mouse then this becomes wrong (even without the
|
||||
// stuff here)
|
||||
// TODO uhhh that's true for all kinds of kb shortcuts actually, even for pressing/releasing
|
||||
// ctrl or shift to change the target. dang
|
||||
|
||||
let cell = this.cell(cell_x, cell_y);
|
||||
let layer = this._find_target_tile(cell);
|
||||
this.hovered_layer = layer;
|
||||
|
||||
if (layer === null) {
|
||||
this.cursor_element.classList.remove('--visible');
|
||||
return;
|
||||
}
|
||||
|
||||
this.cursor_element.classList.add('--visible');
|
||||
if (layer === LAYERS.terrain) {
|
||||
this.cursor_element.setAttribute('data-layer', 'terrain');
|
||||
}
|
||||
else if (layer === LAYERS.item) {
|
||||
this.cursor_element.setAttribute('data-layer', 'item');
|
||||
}
|
||||
else if (layer === LAYERS.actor) {
|
||||
this.cursor_element.setAttribute('data-layer', 'actor');
|
||||
}
|
||||
else if (layer === LAYERS.thin_wall) {
|
||||
this.cursor_element.setAttribute('data-layer', 'thin-wall');
|
||||
}
|
||||
}
|
||||
|
||||
handle_press() {
|
||||
let cell = this.cell(this.prev_cell_x, this.prev_cell_y);
|
||||
if (this.hovered_layer === null)
|
||||
return;
|
||||
let tile = cell[this.hovered_layer];
|
||||
if (! tile)
|
||||
return;
|
||||
|
||||
let rotated;
|
||||
tile = {...tile}; // TODO little inefficient
|
||||
if (this.alt_mode) {
|
||||
// Reverse, go counterclockwise
|
||||
rotated = this.editor.rotate_tile_left(tile);
|
||||
}
|
||||
else {
|
||||
rotated = this.editor.rotate_tile_right(tile);
|
||||
}
|
||||
if (rotated) {
|
||||
this.editor.place_in_cell(cell, tile);
|
||||
this.editor.commit_undo();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Rotate tool doesn't support dragging
|
||||
// TODO should it?
|
||||
}
|
||||
|
||||
// Tiles the "adjust" tool will turn into each other
|
||||
const ADJUST_TOGGLES_CW = {};
|
||||
const ADJUST_TOGGLES_CCW = {};
|
||||
const ADJUST_TILE_TYPES = {};
|
||||
const ADJUST_GATE_TYPES = {};
|
||||
{
|
||||
for (let cycle of [
|
||||
['chip', 'chip_extra'],
|
||||
// TODO shouldn't this convert regular walls into regular floors then?
|
||||
['floor_custom_green', 'wall_custom_green'],
|
||||
['floor_custom_pink', 'wall_custom_pink'],
|
||||
['floor_custom_yellow', 'wall_custom_yellow'],
|
||||
['floor_custom_blue', 'wall_custom_blue'],
|
||||
['fake_floor', 'fake_wall'],
|
||||
['popdown_floor', 'popdown_wall'],
|
||||
['wall_invisible', 'wall_appearing'],
|
||||
['green_floor', 'green_wall'],
|
||||
['green_bomb', 'green_chip'],
|
||||
['purple_floor', 'purple_wall'],
|
||||
['thief_keys', 'thief_tools'],
|
||||
// Try to make these intuitive, the kind of things someone would naturally want to alter in a
|
||||
// very small way. The "other one".
|
||||
for (let [verb, ...cycle] of [
|
||||
["Swap", 'player', 'player2'],
|
||||
["Swap", 'chip', 'chip_extra'],
|
||||
// TODO shouldn't this convert regular walls into regular floors then? or... steel, if it
|
||||
// has wires in it...?
|
||||
// TODO annoying that there are two obvious kinds of change to make here
|
||||
["Recolor", 'floor_custom_pink', 'floor_custom_blue', 'floor_custom_yellow', 'floor_custom_green'],
|
||||
["Recolor", 'wall_custom_pink', 'wall_custom_blue', 'wall_custom_yellow', 'wall_custom_green'],
|
||||
["Recolor", 'door_red', 'door_blue', 'door_yellow', 'door_green'],
|
||||
["Recolor", 'key_red', 'key_blue', 'key_yellow', 'key_green'],
|
||||
["Recolor", 'teleport_red', 'teleport_blue', 'teleport_yellow', 'teleport_green'],
|
||||
["Recolor", 'gate_red', 'gate_blue', 'gate_yellow', 'gate_green'],
|
||||
["Toggle", 'green_floor', 'green_wall'],
|
||||
["Toggle", 'green_bomb', 'green_chip'],
|
||||
["Toggle", 'purple_floor', 'purple_wall'],
|
||||
["Swap", 'fake_floor', 'fake_wall'],
|
||||
["Swap", 'popdown_floor', 'popdown_wall'],
|
||||
["Swap", 'wall_invisible', 'wall_appearing'],
|
||||
["Swap", 'thief_keys', 'thief_tools'],
|
||||
/*
|
||||
['swivel_nw', 'swivel_ne', 'swivel_se', 'swivel_sw'],
|
||||
['ice_nw', 'ice_ne', 'ice_se', 'ice_sw'],
|
||||
['force_floor_n', 'force_floor_e', 'force_floor_s', 'force_floor_w'],
|
||||
['ice', 'force_floor_all'],
|
||||
['water', 'turtle'],
|
||||
['no_player1_sign', 'no_player2_sign'],
|
||||
['flame_jet_off', 'flame_jet_on'],
|
||||
['light_switch_off', 'light_switch_on'],
|
||||
['stopwatch_bonus', 'stopwatch_penalty'],
|
||||
['turntable_cw', 'turntable_ccw'],
|
||||
*/
|
||||
["Flip", 'force_floor_n', 'force_floor_s'],
|
||||
["Flip", 'force_floor_e', 'force_floor_w'],
|
||||
["Swap", 'ice', 'force_floor_all'],
|
||||
["Swap", 'water', 'turtle'],
|
||||
["Swap", 'no_player1_sign', 'no_player2_sign'],
|
||||
["Toggle", 'flame_jet_off', 'flame_jet_on'],
|
||||
["Flip", 'light_switch_off', 'light_switch_on'],
|
||||
["Swap", 'stopwatch_bonus', 'stopwatch_penalty'],
|
||||
["Swap", 'turntable_cw', 'turntable_ccw'],
|
||||
["Swap", 'score_10', 'score_100', 'score_1000'],
|
||||
["Swap", 'dirt_block', 'ice_block'],
|
||||
|
||||
["Swap", 'doppelganger1', 'doppelganger2'],
|
||||
["Swap", 'ball', 'tank_blue'],
|
||||
["Swap", 'fireball', 'glider'],
|
||||
["Swap", 'bug', 'paramecium'],
|
||||
["Swap", 'walker', 'blob'],
|
||||
["Swap", 'teeth', 'teeth_timid'],
|
||||
])
|
||||
{
|
||||
for (let [i, tile] of cycle.entries()) {
|
||||
let other = cycle[(i + 1) % cycle.length];
|
||||
ADJUST_TOGGLES_CW[tile] = other;
|
||||
ADJUST_TOGGLES_CCW[other] = tile;
|
||||
for (let [i, type] of cycle.entries()) {
|
||||
ADJUST_TILE_TYPES[type] = {
|
||||
verb,
|
||||
next: cycle[(i + 1) % cycle.length],
|
||||
prev: cycle[(i - 1 + cycle.length) % cycle.length],
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
for (let cycle of [
|
||||
['not', 'diode'],
|
||||
['and', 'or', 'xor', 'nand'],
|
||||
['latch-cw', 'latch-ccw'],
|
||||
])
|
||||
{
|
||||
for (let [i, type] of cycle.entries()) {
|
||||
ADJUST_GATE_TYPES[type] = {
|
||||
next: cycle[(i + 1) % cycle.length],
|
||||
prev: cycle[(i - 1 + cycle.length) % cycle.length],
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1259,6 +1437,7 @@ const ADJUST_SPECIAL = {
|
||||
},
|
||||
button_gray(editor, tile, cell) {
|
||||
// Toggle gray objects... er... objects affected by gray buttons
|
||||
// TODO right-click should allow toggling backwards!
|
||||
for (let dy = -2; dy <= 2; dy++) {
|
||||
for (let dx = -2; dx <= 2; dx++) {
|
||||
if (dx === 0 && dy === 0)
|
||||
@ -1276,57 +1455,223 @@ const ADJUST_SPECIAL = {
|
||||
}
|
||||
},
|
||||
};
|
||||
// TODO maybe better visual feedback of what will happen when you click?
|
||||
// - rotate terrain (cw, ccw)
|
||||
// - change terrain
|
||||
// - rotate actor (cw, ccw)
|
||||
// - press button
|
||||
// FIXME the preview is not very good because the hover effect becomes stale, pressing ctrl/shift
|
||||
// leaves it stale, etc
|
||||
// FIXME it might be nice to actually preview what we intend to do, which would require just, uh,
|
||||
// doing it to a temporary tile, but actually that does sound a lot better than all this
|
||||
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', {
|
||||
this.gray_button_bounds_rect = mk_svg('rect.overlay-adjust-gray-button-radius', {
|
||||
x: -2,
|
||||
y: -2,
|
||||
width: 5,
|
||||
height: 5,
|
||||
}));
|
||||
});
|
||||
this.gray_button_preview.append(this.gray_button_bounds_rect);
|
||||
this.editor.svg_overlay.append(this.gray_button_preview);
|
||||
|
||||
// Cool octagon
|
||||
/*
|
||||
this.set_cursor_element(mk_svg('path.overlay-transient.overlay-adjust-cursor', {
|
||||
//d: 'M -0.25,-0.25 L 0.5,-0.5 L 1.25,-0.25 L 1.5,0.5' +
|
||||
// 'L 1.25,1.25 L 0.5,1.5 L -0.25,1.25 L -0.5,0.5 z',
|
||||
//d: 'M 0.5,0.5 m 0.75,-0.75 l 0.75,-0.25 l 0.75,0.25 l 0.25,0.75' +
|
||||
// 'l -0.25,0.75 l -0.75,0.25 l -0.75,-0.25 l -0.25,-0.75 z',
|
||||
d: 'M 0.5,0.5 m -0.5,-0.5 l 0.5,-0.125 l 0.5,0.125 l 0.125,0.5' +
|
||||
'l -0.125,0.5 l -0.5,0.125 l -0.5,-0.125 l -0.125,-0.5 z',
|
||||
}));
|
||||
*/
|
||||
// The cursor is the tile being targeted, drawn with high opacity atop the rest of the cell,
|
||||
// to hopefully make it clear which layer we're looking at
|
||||
let renderer = this.editor.renderer;
|
||||
this.canvas = mk('canvas', {
|
||||
width: renderer.tileset.size_x,
|
||||
height: renderer.tileset.size_y,
|
||||
});
|
||||
// Need an extra <g> here so the translate transform doesn't clobber the scale on the
|
||||
// foreignObject
|
||||
this.set_cursor_element(mk_svg('g.overlay-transient',
|
||||
mk_svg('foreignObject', {
|
||||
x: 0,
|
||||
y: 0,
|
||||
width: this.canvas.width,
|
||||
height: this.canvas.height,
|
||||
transform: `scale(${1/renderer.tileset.size_x} ${1/renderer.tileset.size_y})`,
|
||||
opacity: 0.75,
|
||||
}, this.canvas),
|
||||
));
|
||||
|
||||
this.click_hint = mk_svg('text.overlay-adjust-hint.overlay-transient');
|
||||
this.editor.svg_overlay.append(this.click_hint);
|
||||
|
||||
this.hovered_layer = null;
|
||||
}
|
||||
|
||||
_find_target_tile(cell) {
|
||||
let top_layer = LAYERS.MAX - 1;
|
||||
let bottom_layer = 0;
|
||||
if (this.ctrl) {
|
||||
// ctrl: explicitly target terrain
|
||||
top_layer = LAYERS.terrain;
|
||||
bottom_layer = LAYERS.terrain;
|
||||
}
|
||||
else if (this.shift) {
|
||||
// shift: explicitly target actor
|
||||
top_layer = LAYERS.actor;
|
||||
bottom_layer = LAYERS.actor;
|
||||
}
|
||||
for (let layer = top_layer; layer >= bottom_layer; layer--) {
|
||||
let tile = cell[layer];
|
||||
if (! tile)
|
||||
continue;
|
||||
|
||||
// This is kind of like documentation for everything the adjust tool can do I guess
|
||||
if (TILE_TYPES['transmogrifier']._mogrifications[tile.type.name]) {
|
||||
// Toggle between related tile types
|
||||
return [layer, "Mogrify"];
|
||||
}
|
||||
if (ADJUST_TILE_TYPES[tile.type.name]) {
|
||||
// Toggle between related tile types
|
||||
return [layer, ADJUST_TILE_TYPES[tile.type.name].verb];
|
||||
}
|
||||
if (tile.type.name === 'logic_gate' && ADJUST_GATE_TYPES[tile.gate_type]) {
|
||||
// Also toggle between related logic gate types
|
||||
return [layer, "Change"];
|
||||
}
|
||||
if (tile.type.name === 'logic_gate' && tile.gate_type === 'counter') {
|
||||
// Adjust the starting number on a logic gate
|
||||
return [layer, "Count"];
|
||||
}
|
||||
if (layer === LAYERS.thin_wall) {
|
||||
// Place or delete individual thin walls
|
||||
return [layer, "Place"];
|
||||
}
|
||||
if (tile.type.name === 'frame block') {
|
||||
// Place or delete individual frame block arrows
|
||||
return [layer, "Place"];
|
||||
}
|
||||
|
||||
// These are
|
||||
// TODO need a single-click thing to do for
|
||||
let behavior = SPECIAL_TILE_BEHAVIOR[tile.type.name];
|
||||
if (behavior && behavior.adjust_forward) {
|
||||
//
|
||||
return [layer, "Adjust"];
|
||||
}
|
||||
|
||||
if (TILES_WITH_PROPS[tile.type.name]) {
|
||||
// Open special tile editors
|
||||
return [layer, "Edit"];
|
||||
}
|
||||
|
||||
if (ADJUST_SPECIAL[tile.type.name]) {
|
||||
return [layer, "Press"];
|
||||
}
|
||||
}
|
||||
|
||||
return [null, null];
|
||||
}
|
||||
|
||||
handle_hover(client_x, client_y, frac_cell_x, frac_cell_y, cell_x, cell_y) {
|
||||
// TODO hrmm if we undo without moving the mouse then this becomes wrong (even without the
|
||||
// stuff here)
|
||||
// TODO uhhh that's true for all kinds of kb shortcuts actually, even for pressing/releasing
|
||||
// ctrl or shift to change the target. dang
|
||||
if (cell_x === this.prev_cell_x && cell_y === this.prev_cell_y)
|
||||
return;
|
||||
|
||||
let cell = this.cell(cell_x, cell_y);
|
||||
let terrain = cell[LAYERS.terrain];
|
||||
if (terrain.type.name === 'button_gray') {
|
||||
let [layer, hint] = this._find_target_tile(cell);
|
||||
this.hovered_layer = layer;
|
||||
if (hint === null) {
|
||||
this.click_hint.classList.remove('--visible');
|
||||
}
|
||||
else {
|
||||
this.click_hint.classList.add('--visible');
|
||||
this.click_hint.setAttribute('x', cell_x + 0.5);
|
||||
this.click_hint.setAttribute('y', cell_y - 0.125);
|
||||
this.click_hint.textContent = hint;
|
||||
}
|
||||
|
||||
if (layer === null) {
|
||||
this.cursor_element.classList.remove('--visible');
|
||||
this.gray_button_preview.classList.remove('--visible');
|
||||
return;
|
||||
}
|
||||
let tile = cell[layer];
|
||||
|
||||
/*
|
||||
this.cursor_element.classList.add('--visible');
|
||||
if (layer === LAYERS.terrain) {
|
||||
this.cursor_element.setAttribute('data-layer', 'terrain');
|
||||
}
|
||||
else if (layer === LAYERS.item) {
|
||||
this.cursor_element.setAttribute('data-layer', 'item');
|
||||
}
|
||||
else if (layer === LAYERS.actor) {
|
||||
this.cursor_element.setAttribute('data-layer', 'actor');
|
||||
}
|
||||
else if (layer === LAYERS.thin_wall) {
|
||||
this.cursor_element.setAttribute('data-layer', 'thin-wall');
|
||||
}
|
||||
*/
|
||||
|
||||
if (cell.filter(t => t).length <= 1) {
|
||||
// Only one tile, so the canvas is pointless
|
||||
this.cursor_element.classList.remove('--visible');
|
||||
}
|
||||
else {
|
||||
// Draw the targeted tile on top of everything else
|
||||
this.cursor_element.classList.add('--visible');
|
||||
let ctx = this.canvas.getContext('2d');
|
||||
ctx.clearRect(0, 0, this.canvas.width, this.canvas.height);
|
||||
if (layer !== LAYERS.terrain) {
|
||||
this.editor.renderer.draw_single_tile_type('floor', null, this.canvas);
|
||||
}
|
||||
this.editor.renderer.draw_single_tile_type(tile.type.name, tile, this.canvas);
|
||||
}
|
||||
|
||||
// Special previewing behavior
|
||||
if (tile.type.name === 'button_gray') {
|
||||
this.cursor_element.classList.remove('--visible');
|
||||
this.gray_button_preview.classList.add('--visible');
|
||||
this.gray_button_preview.setAttribute('transform', `translate(${cell_x} ${cell_y})`);
|
||||
let gx0 = Math.max(0, cell_x - 2);
|
||||
let gy0 = Math.max(0, cell_y - 2);
|
||||
let gx1 = Math.min(this.editor.stored_level.size_x - 1, cell_x + 2);
|
||||
let gy1 = Math.min(this.editor.stored_level.size_y - 1, cell_y + 2);
|
||||
|
||||
this.gray_button_bounds_rect.setAttribute('x', gx0);
|
||||
this.gray_button_bounds_rect.setAttribute('y', gy0);
|
||||
this.gray_button_bounds_rect.setAttribute('width', gx1 - gx0 + 1);
|
||||
this.gray_button_bounds_rect.setAttribute('height', gy1 - gy0 + 1);
|
||||
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);
|
||||
for (let y = gy0; y <= gy1; y++) {
|
||||
let last_rect, last_x;
|
||||
for (let x = gx0; x <= gx1; x++) {
|
||||
let target = this.cell(x, y);
|
||||
if (target && target !== cell && target[LAYERS.terrain].type.on_gray_button)
|
||||
continue;
|
||||
|
||||
if (last_rect && last_dx === dx - 1) {
|
||||
if (last_rect && last_x === x - 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,
|
||||
x: x,
|
||||
y: y,
|
||||
width: 1,
|
||||
height: 1,
|
||||
});
|
||||
this.gray_button_preview.append(last_rect);
|
||||
}
|
||||
last_dx = dx;
|
||||
last_x = x;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1337,53 +1682,72 @@ export class AdjustOperation extends MouseOperation {
|
||||
|
||||
handle_press() {
|
||||
let cell = this.cell(this.prev_cell_x, this.prev_cell_y);
|
||||
if (this.ctrl) {
|
||||
for (let tile of cell) {
|
||||
if (tile && TILES_WITH_PROPS[tile.type.name] !== undefined) {
|
||||
this.editor.open_tile_prop_overlay(
|
||||
tile, cell, this.editor.renderer.get_cell_rect(cell.x, cell.y));
|
||||
break;
|
||||
}
|
||||
}
|
||||
let tile = cell[this.hovered_layer];
|
||||
if (! tile)
|
||||
return;
|
||||
}
|
||||
let start_layer = this.shift ? 0 : LAYERS.MAX - 1;
|
||||
for (let layer = start_layer; layer >= 0; layer--) {
|
||||
let tile = cell[layer];
|
||||
if (! tile)
|
||||
continue;
|
||||
let behavior = SPECIAL_TILE_BEHAVIOR[tile.type.name];
|
||||
|
||||
let rotated;
|
||||
tile = {...tile}; // TODO little inefficient
|
||||
if (this.alt_mode) {
|
||||
// Reverse, go counterclockwise
|
||||
rotated = this.editor.rotate_tile_left(tile);
|
||||
// Same order as _find_target_tile
|
||||
if (TILE_TYPES['transmogrifier']._mogrifications[tile.type.name]) {
|
||||
// Toggle between related tile types
|
||||
tile.type = TILE_TYPES[TILE_TYPES['transmogrifier']._mogrifications[tile.type.name]];
|
||||
this.editor.place_in_cell(cell, tile);
|
||||
this.editor.commit_undo();
|
||||
}
|
||||
else if (ADJUST_TILE_TYPES[tile.type.name]) {
|
||||
// Toggle between related tile types
|
||||
// TODO can you go backwards any more, or no?
|
||||
let toggled = ADJUST_TILE_TYPES[tile.type.name].next;
|
||||
tile.type = TILE_TYPES[toggled];
|
||||
this.editor.place_in_cell(cell, tile);
|
||||
this.editor.commit_undo();
|
||||
}
|
||||
else if (tile.type.name === 'logic_gate' && ADJUST_GATE_TYPES[tile.gate_type]) {
|
||||
// Also toggle between related logic gate types
|
||||
let toggled = ADJUST_GATE_TYPES[tile.gate_type].next;
|
||||
tile.gate_type = toggled;
|
||||
this.editor.place_in_cell(cell, tile);
|
||||
this.editor.commit_undo();
|
||||
}
|
||||
else if (tile.type.name === 'logic_gate' && tile.gate_type === 'counter') {
|
||||
// Adjust the starting number on a logic gate
|
||||
// TODO is this in adjust_forward or...?
|
||||
}
|
||||
else if (this.hovered_layer === LAYERS.thin_wall) {
|
||||
// Place or delete individual thin walls
|
||||
// XXX don't allow deleting ALL the thin walls...??
|
||||
let bit = DIRECTIONS[this.get_tile_edge()].bit;
|
||||
tile.edges ^= bit;
|
||||
this.editor.place_in_cell(cell, tile);
|
||||
this.editor.commit_undo();
|
||||
}
|
||||
else if (tile.type.name === 'frame_block') {
|
||||
// Place or delete individual frame block arrows
|
||||
let edge = this.get_tile_edge();
|
||||
tile.arrows = new Set(tile.arrows);
|
||||
if (tile.arrows.has(edge)) {
|
||||
tile.arrows.delete(edge);
|
||||
}
|
||||
else {
|
||||
rotated = this.editor.rotate_tile_right(tile);
|
||||
}
|
||||
if (rotated) {
|
||||
this.editor.place_in_cell(cell, tile);
|
||||
this.editor.commit_undo();
|
||||
break;
|
||||
}
|
||||
|
||||
// Toggle tiles that go in obvious pairs
|
||||
let toggled = (this.alt_mode ? ADJUST_TOGGLES_CCW : ADJUST_TOGGLES_CW)[tile.type.name];
|
||||
if (toggled) {
|
||||
tile.type = TILE_TYPES[toggled];
|
||||
this.editor.place_in_cell(cell, tile);
|
||||
this.editor.commit_undo();
|
||||
break;
|
||||
}
|
||||
|
||||
// Other special tile behavior
|
||||
let special = ADJUST_SPECIAL[tile.type.name];
|
||||
if (special) {
|
||||
special(this.editor, tile, cell);
|
||||
this.editor.commit_undo();
|
||||
break;
|
||||
tile.arrows.add(edge);
|
||||
}
|
||||
this.editor.place_in_cell(cell, tile);
|
||||
this.editor.commit_undo();
|
||||
}
|
||||
else if (behavior && behavior.adjust_forward) {
|
||||
behavior.adjust_forward(tile);
|
||||
this.editor.place_in_cell(cell, tile);
|
||||
this.editor.commit_undo();
|
||||
}
|
||||
else if (ADJUST_SPECIAL[tile.type.name]) {
|
||||
ADJUST_SPECIAL[tile.type.name](this.editor, tile, cell);
|
||||
this.editor.commit_undo();
|
||||
}
|
||||
else if (TILES_WITH_PROPS[tile.type.name]) {
|
||||
// Open special tile editors -- this is a last resort, which is why right-click does it
|
||||
// explicitly
|
||||
this.editor.open_tile_prop_overlay(
|
||||
tile, cell, this.editor.renderer.get_cell_rect(cell.x, cell.y));
|
||||
}
|
||||
}
|
||||
// Adjust tool doesn't support dragging
|
||||
@ -1391,6 +1755,7 @@ export class AdjustOperation extends MouseOperation {
|
||||
// TODO if it does then it should end as soon as you spawn a popup
|
||||
do_destroy() {
|
||||
this.gray_button_preview.remove();
|
||||
this.click_hint.remove();
|
||||
super.do_destroy();
|
||||
}
|
||||
}
|
||||
|
||||
@ -1222,6 +1222,7 @@ const TILE_TYPES = {
|
||||
can_reveal_walls: true,
|
||||
can_reverse_on_railroad: true,
|
||||
movement_speed: 4,
|
||||
// TODO why does this have a Set where most things have a bitmask
|
||||
allows_push(me, direction) {
|
||||
return me.arrows && me.arrows.has(direction);
|
||||
},
|
||||
|
||||
43
style.css
@ -1511,10 +1511,10 @@ body.--debug .player-overlay-message {
|
||||
}
|
||||
.player-overlay-message[data-reason=failure] {
|
||||
background: hsla(330, 20%, 10%, 0.5);
|
||||
background: radial-gradient(#0004, hsla(330, 10%, 10%, 0.5) 40%, hsl(330, 20%, 10%));
|
||||
background: radial-gradient(hsla(330, 10%, 10%, 0.75) 40%, hsl(330, 20%, 10%));
|
||||
}
|
||||
.player-overlay-message[data-reason=success] {
|
||||
background: radial-gradient(hsla(30, 80%, 10%, 0.75), 60%, hsla(40, 100%, 30%, 0.75));
|
||||
background: radial-gradient(hsla(40, 80%, 10%, 0.75), hsla(40, 80%, 20%, 0.875) 80%, hsla(40, 80%, 30%, 0.875));
|
||||
}
|
||||
.player-overlay-message[data-reason=ended] {
|
||||
/* Rearrange this entirely, to fit the ending image in */
|
||||
@ -2198,7 +2198,7 @@ body.--debug #player-debug {
|
||||
width: -moz-fit-content;
|
||||
width: fit-content;
|
||||
}
|
||||
#editor .editor-canvas canvas {
|
||||
#editor .editor-canvas canvas.editor-renderer-canvas {
|
||||
display: block;
|
||||
width: calc(var(--viewport-width) * var(--tile-width) * var(--scale));
|
||||
--viewport-width: 9;
|
||||
@ -2208,10 +2208,7 @@ body.--debug #player-debug {
|
||||
/* SVG overlays */
|
||||
svg.level-editor-overlay {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
inset: calc(-1 * var(--tile-width) * var(--scale)) calc(-1 * var(--tile-height) * var(--scale));
|
||||
/* allow clicks to go through us! */
|
||||
pointer-events: none;
|
||||
|
||||
@ -2279,7 +2276,7 @@ svg.level-editor-overlay g.overlay-connection[data-source=button_red] {
|
||||
stroke: hsl(0, 90%, 60%);
|
||||
}
|
||||
svg.level-editor-overlay g.overlay-connection[data-source=button_brown] {
|
||||
stroke: hsl(20, 60%, 60%);
|
||||
stroke: hsl(50, 90%, 50%);
|
||||
}
|
||||
svg.level-editor-overlay g.overlay-connection[data-source=button_orange] {
|
||||
stroke: hsl(30, 90%, 60%);
|
||||
@ -2290,6 +2287,27 @@ 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-cursor {
|
||||
/* shared between rotate+adjust tools, though they use different elements/shapes */
|
||||
stroke: #444;
|
||||
fill: #fff4;
|
||||
}
|
||||
svg.level-editor-overlay .overlay-adjust-cursor[data-layer=terrain] {
|
||||
stroke: hsl(150deg, 80%, 20%, 0.8);
|
||||
fill: hsl(150deg, 80%, 60%, 0.4);
|
||||
}
|
||||
svg.level-editor-overlay .overlay-adjust-cursor[data-layer=item] {
|
||||
stroke: hsl(50deg, 80%, 20%, 0.8);
|
||||
fill: hsl(50deg, 80%, 60%, 0.4);
|
||||
}
|
||||
svg.level-editor-overlay .overlay-adjust-cursor[data-layer=actor] {
|
||||
stroke: hsl(215deg, 80%, 20%, 0.8);
|
||||
fill: hsl(215deg, 80%, 60%, 0.4);
|
||||
}
|
||||
svg.level-editor-overlay .overlay-adjust-cursor[data-layer=thin-wall] {
|
||||
stroke: hsl(330deg, 80%, 20%, 0.8);
|
||||
fill: hsl(330deg, 80%, 60%, 0.4);
|
||||
}
|
||||
svg.level-editor-overlay .overlay-adjust-gray-button-radius {
|
||||
stroke: #f4f4f4;
|
||||
fill: hsla(10, 10%, 80%, 0.125);
|
||||
@ -2314,6 +2332,15 @@ svg.level-editor-overlay text.overlay-edit-tip {
|
||||
text-anchor: middle;
|
||||
dominant-baseline: middle;
|
||||
}
|
||||
svg.level-editor-overlay text.overlay-adjust-hint {
|
||||
font-size: calc(0.5px / var(--scale));
|
||||
font-weight: bold;
|
||||
stroke: black;
|
||||
fill: white;
|
||||
paint-order: stroke;
|
||||
text-anchor: middle;
|
||||
dominant-baseline: auto;
|
||||
}
|
||||
|
||||
.editor-big-tooltip {
|
||||
/* shared between toolbar and palette tooltips */
|
||||
|
||||