Merge trap/cloner connections; round-trip them through C2M; stub out connect tool
This commit is contained in:
parent
7f90ee5f7d
commit
eff62a9765
@ -127,12 +127,9 @@ export class StoredLevel extends LevelInterface {
|
||||
this.size_y = 0;
|
||||
this.linear_cells = [];
|
||||
|
||||
// Maps of button positions to trap/cloner positions, as scalar indexes
|
||||
// in the linear cell list
|
||||
// TODO merge these imo
|
||||
// Maps of button positions to trap/cloner positions, as scalars
|
||||
this.has_custom_connections = false;
|
||||
this.custom_trap_wiring = {};
|
||||
this.custom_cloner_wiring = {};
|
||||
this.custom_connections = {};
|
||||
|
||||
// New LL feature: custom camera regions, as lists of {x, y, width, height}
|
||||
this.camera_regions = [];
|
||||
|
||||
@ -1307,6 +1307,20 @@ export function parse_level(buf, number = 1) {
|
||||
p += 4;
|
||||
}
|
||||
}
|
||||
else if (type === 'LXCX') {
|
||||
// Custom connections, like MSCC (but more! maybe)
|
||||
if (bytes.length % 4 !== 0)
|
||||
throw new Error(`Expected LXCX chunk to be a multiple of 4 bytes; got ${bytes.length}`);
|
||||
|
||||
level.has_custom_connections = true;
|
||||
let p = 0;
|
||||
while (p < bytes.length) {
|
||||
let src = view.getUint16(p, true);
|
||||
let dest = view.getUint16(p + 2, true);
|
||||
level.custom_connections[src] = dest;
|
||||
p += 4;
|
||||
}
|
||||
}
|
||||
else {
|
||||
console.warn(`Unrecognized section type '${type}' at offset ${bytes.byteOffset}`, view);
|
||||
// TODO save it, persist when editing level
|
||||
@ -1536,6 +1550,21 @@ export function synthesize_level(stored_level) {
|
||||
c2m.add_section('LXCM', bytes.buffer);
|
||||
}
|
||||
|
||||
// Store MSCC-like custom connections
|
||||
// TODO LL feature, should be distinguished somehow
|
||||
let num_connections = Object.keys(stored_level.custom_connections).length;
|
||||
if (num_connections > 0) {
|
||||
let buf = new ArrayBuffer(4 * num_connections);
|
||||
let view = new DataView(buf);
|
||||
let p = 0;
|
||||
for (let [src, dest] of Object.entries(stored_level.custom_connections)) {
|
||||
view.setUint16(p + 0, src, true);
|
||||
view.setUint16(p + 2, dest, true);
|
||||
p += 4;
|
||||
}
|
||||
c2m.add_section('LXCX', buf);
|
||||
}
|
||||
|
||||
let map_bytes = new Uint8Array(1024);
|
||||
let map_view = new DataView(map_bytes.buffer);
|
||||
map_bytes[0] = stored_level.size_x;
|
||||
|
||||
@ -348,7 +348,13 @@ function parse_level(bytes, number) {
|
||||
let trap_y = field_view.getUint16(q + 6, true);
|
||||
// Fifth u16 is always zero, possibly live game state
|
||||
q += 10;
|
||||
level.custom_trap_wiring[button_x + button_y * level.size_x] = trap_x + trap_y * level.size_x;
|
||||
// Connections are ignored if they're on the wrong tiles anyway, and we use a single
|
||||
// mapping that's a bit more flexible, so only store valid connections
|
||||
let s = level.coords_to_scalar(button_x, button_y);
|
||||
let d = level.coords_to_scalar(trap_x, trap_y);
|
||||
if (level.linear_cells[s][LAYERS.terrain].type.name === 'button_brown') {
|
||||
level.custom_connections[s] = d;
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (field_type === 0x05) {
|
||||
@ -361,7 +367,13 @@ function parse_level(bytes, number) {
|
||||
let cloner_x = field_view.getUint16(q + 4, true);
|
||||
let cloner_y = field_view.getUint16(q + 6, true);
|
||||
q += 8;
|
||||
level.custom_cloner_wiring[button_x + button_y * level.size_x] = cloner_x + cloner_y * level.size_x;
|
||||
// Connections are ignored if they're on the wrong tiles anyway, and we use a single
|
||||
// mapping that's a bit more flexible, so only store valid connections
|
||||
let s = level.coords_to_scalar(button_x, button_y);
|
||||
let d = level.coords_to_scalar(cloner_x, cloner_y);
|
||||
if (level.linear_cells[s][LAYERS.terrain].type.name === 'button_red') {
|
||||
level.custom_connections[s] = d;
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (field_type === 0x06) {
|
||||
@ -460,9 +472,11 @@ export function synthesize_level(stored_level) {
|
||||
let top_layer = [];
|
||||
let bottom_layer = [];
|
||||
let hint_text = null;
|
||||
let monster_coords = [];
|
||||
let error_found_wires = false;
|
||||
// TODO i could be a little kinder and support, say, items on terrain; do those work in mscc? tw lynx?
|
||||
for (let [i, cell] of stored_level.linear_cells.entries()) {
|
||||
let [x, y] = stored_level.scalar_to_coords(i);
|
||||
let actor = null;
|
||||
let other = null;
|
||||
for (let tile of cell) {
|
||||
@ -480,12 +494,15 @@ export function synthesize_level(stored_level) {
|
||||
continue;
|
||||
}
|
||||
else if (other) {
|
||||
let [x, y] = stored_level.scalar_to_coords(i);
|
||||
errors.push(`A cell can only contain one static tile, but cell (${x}, ${y}) has both ${other.type.name} and ${tile.type.name}`);
|
||||
}
|
||||
else {
|
||||
other = tile;
|
||||
}
|
||||
|
||||
if (tile.type.is_monster) {
|
||||
monster_coords.push(x, y);
|
||||
}
|
||||
}
|
||||
|
||||
let actor_byte = null;
|
||||
@ -584,9 +601,14 @@ export function synthesize_level(stored_level) {
|
||||
if (hint_text !== null) {
|
||||
add_block(7, util.bytestring_to_buffer(hint_text.substring(0, 127) + "\0"));
|
||||
}
|
||||
// Monster positions
|
||||
// TODO this is dumb as hell but do it too
|
||||
add_block(10, new ArrayBuffer);
|
||||
// Monster positions (dumb as hell and only used in MS mode)
|
||||
if (monster_coords.length > 0) {
|
||||
if (monster_coords.length > 256) {
|
||||
errors.push(`Level has ${monster_coords.length >> 1} monsters, but MS only supports up to 128`);
|
||||
monster_coords.length = 256;
|
||||
}
|
||||
add_block(10, new Uint8Array(monster_coords).buffer);
|
||||
}
|
||||
|
||||
if (errors.length > 0) {
|
||||
throw new CCLEncodingErrors(errors);
|
||||
|
||||
@ -637,14 +637,12 @@ export class Level extends LevelInterface {
|
||||
|
||||
// Check for custom wiring, for MSCC .DAT levels
|
||||
// TODO would be neat if this applied to orange buttons too
|
||||
// TODO RAINBOW TELEPORTER, ARBITRARY TILE TARGET HAHA
|
||||
if (this.stored_level.has_custom_connections) {
|
||||
let n = this.stored_level.coords_to_scalar(x, y);
|
||||
let target_cell_n = null;
|
||||
if (connectable.type.name === 'button_brown') {
|
||||
target_cell_n = this.stored_level.custom_trap_wiring[n] ?? null;
|
||||
}
|
||||
else if (connectable.type.name === 'button_red') {
|
||||
target_cell_n = this.stored_level.custom_cloner_wiring[n] ?? null;
|
||||
if (connectable.type.name === 'button_brown' || connectable.type.name === 'button_red') {
|
||||
target_cell_n = this.stored_level.custom_connections[n] ?? null;
|
||||
}
|
||||
if (target_cell_n && target_cell_n < this.width * this.height) {
|
||||
let [tx, ty] = this.stored_level.scalar_to_coords(target_cell_n);
|
||||
|
||||
@ -1313,6 +1313,103 @@ class TrackOperation extends MouseOperation {
|
||||
}
|
||||
}
|
||||
|
||||
class SVGConnection {
|
||||
constructor(sx, sy, dx, dy) {
|
||||
this.source = mk_svg('rect.-source', {width: 1, height: 1});
|
||||
this.line = mk_svg('line.-arrow', {});
|
||||
this.element = mk_svg('g.overlay-connection', this.source, this.line);
|
||||
this.set_source(sx, sy);
|
||||
this.set_dest(dx, dy);
|
||||
}
|
||||
|
||||
set_source(sx, sy) {
|
||||
this.sx = sx;
|
||||
this.sy = sy;
|
||||
this.source.setAttribute('x', sx);
|
||||
this.source.setAttribute('y', sy);
|
||||
this.line.setAttribute('x1', sx + 0.5);
|
||||
this.line.setAttribute('y1', sy + 0.5);
|
||||
}
|
||||
|
||||
set_dest(dx, dy) {
|
||||
this.dx = dx;
|
||||
this.dy = dy;
|
||||
this.line.setAttribute('x2', dx + 0.5);
|
||||
this.line.setAttribute('y2', dy + 0.5);
|
||||
}
|
||||
}
|
||||
class ConnectOperation extends MouseOperation {
|
||||
handle_press(x, y, ev) {
|
||||
// TODO restrict to button/cloner unless holding shift
|
||||
// TODO what do i do when you erase a button/cloner? can i detect if you're picking it up?
|
||||
let src = this.editor.stored_level.coords_to_scalar(x, y);
|
||||
if (this.alt_mode) {
|
||||
// Auto connect using Lynx rules
|
||||
let cell = this.cell(x, y);
|
||||
let terrain = cell[LAYERS.terrain];
|
||||
let other = null;
|
||||
let swap = false;
|
||||
if (terrain.type.name === 'button_red') {
|
||||
other = this.search_for(src, 'cloner', 1);
|
||||
}
|
||||
else if (terrain.type.name === 'cloner') {
|
||||
other = this.search_for(src, 'button_red', -1);
|
||||
swap = true;
|
||||
}
|
||||
else if (terrain.type.name === 'button_brown') {
|
||||
other = this.search_for(src, 'trap', 1);
|
||||
}
|
||||
else if (terrain.type.name === 'trap') {
|
||||
other = this.search_for(src, 'button_brown', -1);
|
||||
swap = true;
|
||||
}
|
||||
|
||||
if (other !== null) {
|
||||
if (swap) {
|
||||
this.editor.set_custom_connection(other, src);
|
||||
}
|
||||
else {
|
||||
this.editor.set_custom_connection(src, other);
|
||||
}
|
||||
this.editor.commit_undo();
|
||||
}
|
||||
return;
|
||||
}
|
||||
this.pending_cxn = new SVGConnection(x, y, x, y);
|
||||
this.editor.svg_overlay.append(this.pending_cxn.element);
|
||||
}
|
||||
// FIXME this is hella the sort of thing that should be on Editor, or in algorithms
|
||||
search_for(i0, name, dir) {
|
||||
let l = this.editor.stored_level.linear_cells.length;
|
||||
let i = i0;
|
||||
while (true) {
|
||||
i += dir;
|
||||
if (i < 0) {
|
||||
i += l;
|
||||
}
|
||||
else if (i >= l) {
|
||||
i -= l;
|
||||
}
|
||||
if (i === i0)
|
||||
return null;
|
||||
|
||||
let cell = this.editor.stored_level.linear_cells[i];
|
||||
let tile = cell[LAYERS.terrain];
|
||||
if (tile.type.name === name) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
}
|
||||
handle_drag(client_x, client_y, frac_cell_x, frac_cell_y, cell_x, cell_y) {
|
||||
}
|
||||
commit_press() {
|
||||
}
|
||||
abort_press() {
|
||||
this.pending_cxn.element.remove();
|
||||
}
|
||||
cleanup_press() {
|
||||
}
|
||||
}
|
||||
class WireOperation extends MouseOperation {
|
||||
handle_press(x, y) {
|
||||
if (this.alt_mode) {
|
||||
@ -1825,13 +1922,16 @@ const EDITOR_TOOLS = {
|
||||
shortcut: 'a',
|
||||
},
|
||||
connect: {
|
||||
// TODO not implemented
|
||||
icon: 'icons/tool-connect.png',
|
||||
name: "Connect",
|
||||
desc: "Set up CC1 clone and trap connections",
|
||||
// XXX shouldn't you be able to drag the destination?
|
||||
// TODO mod + right click for RRO or diamond alg? ah but we only have ctrl available
|
||||
desc: "Set up CC1-style clone and trap connections.\n(WIP)\nNOTE: Not supported in CC2!\nRight click: auto link using Lynx rules",
|
||||
//desc: "Set up CC1-style clone and trap connections.\nNOTE: Not supported in CC2!\nLeft drag: link button with valid target\nCtrl-click: erase link\nRight click: auto link using Lynx rules",
|
||||
op1: ConnectOperation,
|
||||
op2: ConnectOperation,
|
||||
},
|
||||
wire: {
|
||||
// TODO not implemented
|
||||
icon: 'icons/tool-wire.png',
|
||||
name: "Wire",
|
||||
desc: "Edit CC2 wiring.\nLeft click: draw wires\nCtrl-click: erase wires\nRight click: toggle tunnels (floor only)",
|
||||
@ -1851,7 +1951,7 @@ const EDITOR_TOOLS = {
|
||||
// slade when you have some selected?
|
||||
// TODO ah, railroads...
|
||||
};
|
||||
const EDITOR_TOOL_ORDER = ['pencil', 'select_box', 'fill', 'adjust', 'force-floors', 'tracks', 'wire', 'camera'];
|
||||
const EDITOR_TOOL_ORDER = ['pencil', 'select_box', 'fill', 'adjust', 'force-floors', 'tracks', 'connect', 'wire', 'camera'];
|
||||
const EDITOR_TOOL_SHORTCUTS = {};
|
||||
for (let [tool, tooldef] of Object.entries(EDITOR_TOOLS)) {
|
||||
if (tooldef.shortcut) {
|
||||
@ -3256,7 +3356,20 @@ export class Editor extends PrimaryView {
|
||||
// FIXME need this in load_level which is called even if we haven't been setup yet
|
||||
this.connections_g = mk_svg('g');
|
||||
// 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.connections_g);
|
||||
this.svg_overlay = mk_svg('svg.level-editor-overlay', {viewBox: '0 0 32 32'},
|
||||
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'}),
|
||||
),
|
||||
),
|
||||
mk_svg('filter', {id: 'overlay-filter-outline'},
|
||||
mk_svg('feMorphology', {'in': 'SourceAlpha', result: 'dilated', operator: 'dilate', radius: 0.03125}),
|
||||
mk_svg('feFlood', {'flood-color': '#0009', result: 'fill'}),
|
||||
mk_svg('feComposite', {'in': 'fill', in2: 'dilated', operator: 'in'}),
|
||||
mk_svg('feComposite', {'in': 'SourceGraphic'}),
|
||||
),
|
||||
this.connections_g,
|
||||
);
|
||||
this.viewport_el.append(this.renderer.canvas, this.svg_overlay);
|
||||
|
||||
// This is done more correctly in setup(), but we need a sensible default so levels can be
|
||||
@ -3436,12 +3549,20 @@ export class Editor extends PrimaryView {
|
||||
this.mouse_coords = [ev.clientX, ev.clientY];
|
||||
// TODO move this into MouseOperation
|
||||
let [x, y] = this.renderer.cell_coords_from_event(ev);
|
||||
if (this.is_in_bounds(x, y)) {
|
||||
// TODO only do this stuff if the cell coords changed
|
||||
let cell = this.cell(x, y);
|
||||
if (cell) {
|
||||
this.svg_cursor.classList.add('--visible');
|
||||
this.svg_cursor.setAttribute('x', x);
|
||||
this.svg_cursor.setAttribute('y', y);
|
||||
|
||||
this.statusbar_cursor.textContent = `(${x}, ${y})`;
|
||||
|
||||
// TODO don't /always/ do this. maybe make it optionally always visible, and have
|
||||
// an inspection tool that does it on point
|
||||
let terrain = cell[LAYERS.terrain];
|
||||
if (terrain.type.name === 'button_gray') {
|
||||
}
|
||||
}
|
||||
else {
|
||||
this.svg_cursor.classList.remove('--visible');
|
||||
@ -3633,27 +3754,12 @@ export class Editor extends PrimaryView {
|
||||
url.searchParams.append('level', data);
|
||||
new EditorShareOverlay(this.conductor, url.toString()).open();
|
||||
}],
|
||||
["Download level as C2M", () => {
|
||||
["Download level as C2M (new CC2 format)", () => {
|
||||
// TODO support getting warnings + errors out of synthesis
|
||||
let buf = c2g.synthesize_level(this.stored_level);
|
||||
util.trigger_local_download((this.stored_level.title || 'untitled') + '.c2m', new Blob([buf]));
|
||||
}],
|
||||
["Download level as MSCC DAT/CCL", () => {
|
||||
// TODO support getting warnings out of synthesis?
|
||||
let buf;
|
||||
try {
|
||||
buf = dat.synthesize_level(this.stored_level);
|
||||
}
|
||||
catch (errs) {
|
||||
if (errs instanceof dat.CCLEncodingErrors) {
|
||||
new EditorExportFailedOverlay(this.conductor, errs.errors).open();
|
||||
return;
|
||||
}
|
||||
throw errs;
|
||||
}
|
||||
util.trigger_local_download((this.stored_level.title || 'untitled') + '.ccl', new Blob([buf]));
|
||||
}],
|
||||
["Download pack as C2G", () => {
|
||||
["Download pack as C2G (new CC2 format)", () => {
|
||||
let stored_pack = this.conductor.stored_game;
|
||||
|
||||
// This is pretty heckin' best-effort for now; TODO move into format-c2g?
|
||||
@ -3699,6 +3805,21 @@ export class Editor extends PrimaryView {
|
||||
// TODO support getting warnings + errors out of synthesis
|
||||
util.trigger_local_download((stored_pack.title || 'untitled') + '.zip', new Blob([u8array]));
|
||||
}],
|
||||
["Download level as CCL (old CC1 format)", () => {
|
||||
// TODO support getting warnings out of synthesis?
|
||||
let buf;
|
||||
try {
|
||||
buf = dat.synthesize_level(this.stored_level);
|
||||
}
|
||||
catch (errs) {
|
||||
if (errs instanceof dat.CCLEncodingErrors) {
|
||||
new EditorExportFailedOverlay(this.conductor, errs.errors).open();
|
||||
return;
|
||||
}
|
||||
throw errs;
|
||||
}
|
||||
util.trigger_local_download((this.stored_level.title || 'untitled') + '.ccl', new Blob([buf]));
|
||||
}],
|
||||
];
|
||||
this.export_menu = new MenuOverlay(
|
||||
this.conductor,
|
||||
@ -4186,15 +4307,17 @@ export class Editor extends PrimaryView {
|
||||
}
|
||||
|
||||
// Load connections
|
||||
// TODO cloners too
|
||||
// TODO what if the source tile is not connectable?
|
||||
// TODO there's a has_custom_connections flag, is that important here or is it just because
|
||||
// i can't test an object as a bool
|
||||
this.connections_g.textContent = '';
|
||||
for (let [src, dest] of Object.entries(this.stored_level.custom_trap_wiring)) {
|
||||
this.connections_elements = {};
|
||||
for (let [src, dest] of Object.entries(this.stored_level.custom_connections)) {
|
||||
let [sx, sy] = this.stored_level.scalar_to_coords(src);
|
||||
let [dx, dy] = this.stored_level.scalar_to_coords(dest);
|
||||
this.connections_g.append(
|
||||
mk_svg('rect.overlay-cxn', {x: sx, y: sy, width: 1, height: 1}),
|
||||
mk_svg('line.overlay-cxn', {x1: sx + 0.5, y1: sy + 0.5, x2: dx + 0.5, y2: dy + 0.5}),
|
||||
);
|
||||
let el = new SVGConnection(sx, sy, dx, dy).element;
|
||||
this.connections_elements[src] = el;
|
||||
this.connections_g.append(el);
|
||||
}
|
||||
// TODO why are these in connections_g lol
|
||||
for (let [i, region] of this.stored_level.camera_regions.entries()) {
|
||||
@ -4505,16 +4628,11 @@ export class Editor extends PrimaryView {
|
||||
// Utility/inspection
|
||||
|
||||
is_in_bounds(x, y) {
|
||||
return 0 <= x && x < this.stored_level.size_x && 0 <= y && y < this.stored_level.size_y;
|
||||
return this.stored_level.is_point_within_bounds(x, y);
|
||||
}
|
||||
|
||||
cell(x, y) {
|
||||
if (this.is_in_bounds(x, y)) {
|
||||
return this.stored_level.linear_cells[this.stored_level.coords_to_scalar(x, y)];
|
||||
}
|
||||
else {
|
||||
return null;
|
||||
}
|
||||
return this.stored_level.cell(x, y);
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------------------------------------
|
||||
@ -4655,6 +4773,35 @@ export class Editor extends PrimaryView {
|
||||
this.commit_undo();
|
||||
}
|
||||
|
||||
// Create a connection between two cells and update the UI accordingly. If dest is null or
|
||||
// undefined, delete any existing connection instead.
|
||||
set_custom_connection(src, dest) {
|
||||
let prev = this.stored_level.custom_connections[src];
|
||||
this._do(
|
||||
() => this._set_custom_connection(src, dest),
|
||||
() => this._set_custom_connection(src, prev),
|
||||
);
|
||||
}
|
||||
_set_custom_connection(src, dest) {
|
||||
if (this.connections_elements[src]) {
|
||||
this.connections_elements[src].remove();
|
||||
}
|
||||
|
||||
if ((dest ?? null) === null) {
|
||||
delete this.stored_level.custom_connections[src];
|
||||
delete this.connections_elements[src];
|
||||
}
|
||||
else {
|
||||
this.stored_level.custom_connections[src] = dest;
|
||||
let el = new SVGConnection(
|
||||
...this.stored_level.scalar_to_coords(src),
|
||||
...this.stored_level.scalar_to_coords(dest),
|
||||
).element;
|
||||
this.connections_elements[src] = el;
|
||||
this.connections_g.append(el);
|
||||
}
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------------------------------------
|
||||
// Undo/redo
|
||||
|
||||
|
||||
16
js/util.js
16
js/util.js
@ -48,6 +48,22 @@ export function mk_svg(tag_selector, ...children) {
|
||||
return _mk(el, children);
|
||||
}
|
||||
|
||||
export function trigger_local_download(filename, blob) {
|
||||
let url = URL.createObjectURL(blob);
|
||||
// To download a file, um, make an <a> and click it. Not kidding
|
||||
let a = mk('a', {
|
||||
href: url,
|
||||
download: filename,
|
||||
});
|
||||
document.body.append(a);
|
||||
a.click();
|
||||
// Absolutely no idea when I'm allowed to revoke this, but surely a minute is safe
|
||||
window.setTimeout(() => {
|
||||
a.remove();
|
||||
URL.revokeObjectURL(url);
|
||||
}, 60 * 1000);
|
||||
}
|
||||
|
||||
export function handle_drop(element, options) {
|
||||
let dropzone_class = options.dropzone_class ?? null;
|
||||
let on_drop = options.on_drop;
|
||||
|
||||
30
style.css
30
style.css
@ -1839,7 +1839,7 @@ body.--debug #player-debug {
|
||||
--scale: 1;
|
||||
}
|
||||
/* SVG overlays */
|
||||
#editor svg.level-editor-overlay {
|
||||
svg.level-editor-overlay {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
@ -1852,21 +1852,21 @@ body.--debug #player-debug {
|
||||
stroke-width: 0.0625;
|
||||
fill: none;
|
||||
}
|
||||
#editor .level-editor-overlay .overlay-transient {
|
||||
svg.level-editor-overlay .overlay-transient {
|
||||
display: none;
|
||||
}
|
||||
#editor .level-editor-overlay .overlay-transient.--visible {
|
||||
svg.level-editor-overlay .overlay-transient.--visible {
|
||||
display: initial;
|
||||
}
|
||||
#editor .level-editor-overlay rect.overlay-cursor {
|
||||
svg.level-editor-overlay rect.overlay-cursor {
|
||||
x-stroke: hsla(225, 100%, 60%, 0.5);
|
||||
fill: hsla(225, 100%, 75%, 0.25);
|
||||
}
|
||||
#editor .level-editor-overlay rect.overlay-pending-selection {
|
||||
svg.level-editor-overlay rect.overlay-pending-selection {
|
||||
stroke: hsla(225, 100%, 60%, 0.5);
|
||||
fill: hsla(225, 100%, 75%, 0.25);
|
||||
}
|
||||
#editor .level-editor-overlay rect.overlay-selection {
|
||||
svg.level-editor-overlay rect.overlay-selection {
|
||||
stroke: #000c;
|
||||
fill: hsla(225, 0%, 75%, 0.25);
|
||||
stroke-dasharray: 0.125, 0.125;
|
||||
@ -1882,22 +1882,26 @@ body.--debug #player-debug {
|
||||
stroke-dashoffset: 0;
|
||||
}
|
||||
}
|
||||
#editor .level-editor-overlay rect.overlay-cxn {
|
||||
stroke: red;
|
||||
#overlay-arrowhead {
|
||||
fill: hsl(345, 75%, 75%);
|
||||
}
|
||||
#editor .level-editor-overlay line.overlay-cxn {
|
||||
stroke: red;
|
||||
svg.level-editor-overlay g.overlay-connection {
|
||||
stroke: hsl(345, 75%, 75%);
|
||||
filter: url(#overlay-filter-outline);
|
||||
}
|
||||
#editor .level-editor-overlay rect.overlay-camera {
|
||||
svg.level-editor-overlay g.overlay-connection line.-arrow {
|
||||
marker-end: url(#overlay-arrowhead);
|
||||
}
|
||||
svg.level-editor-overlay rect.overlay-camera {
|
||||
stroke: #808080;
|
||||
fill: #80808040;
|
||||
pointer-events: auto;
|
||||
}
|
||||
#editor .level-editor-overlay text {
|
||||
svg.level-editor-overlay text {
|
||||
/* Each cell is one "pixel", so text needs to be real small */
|
||||
font-size: 1px;
|
||||
}
|
||||
#editor .level-editor-overlay text.overlay-edit-tip {
|
||||
svg.level-editor-overlay text.overlay-edit-tip {
|
||||
stroke: none;
|
||||
fill: black;
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user