From 32be0d0d71acb009ed4c79af8db5ab997d8aff37 Mon Sep 17 00:00:00 2001 From: "Eevee (Evelyn Woods)" Date: Sat, 19 Sep 2020 22:43:04 -0600 Subject: [PATCH] Overhaul inventory: taking items is now undoable; inventory UI is correct, shows key counts --- js/game.js | 78 ++++++++++++++++++++++++++++++++----------------- js/main.js | 76 ++++++++++++++++++++++++++++++++++------------- js/tiletypes.js | 24 ++++----------- style.css | 28 ++++++++++++++---- 4 files changed, 137 insertions(+), 69 deletions(-) diff --git a/js/game.js b/js/game.js index e769387..c2389ae 100644 --- a/js/game.js +++ b/js/game.js @@ -11,7 +11,8 @@ export class Tile { this.movement_cooldown = 0; if (type.has_inventory) { - this.inventory = {}; + this.keyring = {}; + this.toolbelt = []; } } @@ -64,11 +65,8 @@ export class Tile { if (this.type.ignores && this.type.ignores.has(name)) return true; - if (this.inventory) { - for (let [item, count] of Object.entries(this.inventory)) { - if (count === 0) - continue; - + if (this.toolbelt) { + for (let item of this.toolbelt) { let item_type = TILE_TYPES[item]; if (item_type.item_ignores && item_type.item_ignores.has(name)) return true; @@ -87,24 +85,11 @@ export class Tile { // Inventory stuff has_item(name) { - return this.inventory[name] ?? 0 > 0; - } - - // TODO remove, not undoable - take_item(name, amount = null) { - if (this.inventory[name] && this.inventory[name] >= 1) { - if (amount == null && this.type.infinite_items && this.type.infinite_items[name]) { - // Some items can't be taken away normally, by which I mean, - // green keys - ; - } - else { - this.inventory[name] = Math.max(0, this.inventory[name] - (amount || 1)); - } - return true; + if (TILE_TYPES[name].is_key) { + return this.keyring && (this.keyring[name] ?? 0) > 0; } else { - return false; + return this.toolbelt && this.toolbelt.some(item => item === name); } } } @@ -1009,15 +994,56 @@ export class Level { } give_actor(actor, name) { - if (! actor.type.has_inventory) + if (! actor.type.is_actor) return false; - let current = actor.inventory[name]; - this.pending_undo.push(() => actor.inventory[name] = current); - actor.inventory[name] = (current ?? 0) + 1; + let type = TILE_TYPES[name]; + if (type.is_key) { + actor.keyring ??= {}; + actor.keyring[name] ??= 0; + actor.keyring[name] += 1; + this.pending_undo.push(() => actor.keyring[name] -= 1); + } + else { + // tool, presumably + actor.toolbelt ??= []; + actor.toolbelt.push(name); + this.pending_undo.push(() => actor.toolbelt.pop()); + } return true; } + take_key_from_actor(actor, name) { + if (actor.keyring && (actor.keyring[name] ?? 0) > 0) { + if (actor.type.infinite_items && actor.type.infinite_items[name]) { + // Some items can't be taken away normally, by which I mean, green or yellow keys + return true; + } + + this.pending_undo.push(() => actor.keyring[name] += 1); + actor.keyring[name] -= 1; + return true; + } + + return false; + } + + take_all_keys_from_actor(actor) { + if (actor.keyring) { + let keyring = actor.keyring; + this.pending_undo.push(() => actor.keyring = keyring); + actor.keyring = {}; + } + } + + take_all_tools_from_actor(actor) { + if (actor.toolbelt) { + let toolbelt = actor.toolbelt; + this.pending_undo.push(() => actor.toolbelt = toolbelt); + actor.toolbelt = []; + } + } + // Mark an actor as sliding make_slide(actor, mode) { let current = actor.slide_mode; diff --git a/js/main.js b/js/main.js index a523db7..218f5ef 100644 --- a/js/main.js +++ b/js/main.js @@ -315,6 +315,20 @@ class Player extends PrimaryView { this._inventory_tiles = {}; let floor_tile = this.render_inventory_tile('floor'); this.inventory_el.style.backgroundImage = `url(${floor_tile})`; + this.inventory_key_nodes = {}; + this.inventory_tool_nodes = []; + for (let key of ['key_red', 'key_blue', 'key_yellow', 'key_green']) { + let img = mk('img', {src: this.render_inventory_tile(key)}); + let count = mk('span.-count'); + let root = mk('span', img, count); + this.inventory_key_nodes[key] = {root, img, count}; + this.inventory_el.append(root); + } + for (let i = 0; i < 4; i++) { + let img = mk('img'); + this.inventory_tool_nodes.push(img); + this.inventory_el.append(img); + } let last_key; this.pending_player_move = null; @@ -436,28 +450,25 @@ class Player extends PrimaryView { load_level(stored_level) { this.level = new Level(stored_level, this.compat); this.renderer.set_level(this.level); - // waiting: haven't yet pressed a key so the timer isn't going - // playing: playing normally - // paused: um, paused - // rewinding: playing backwards - // stopped: level has ended one way or another - this.set_state('waiting'); - - this.tic_offset = 0; - this.last_advance = 0; - - this.demo_faucet = null; this.root.classList.toggle('--has-demo', !!this.level.stored_level.demo); - - this.update_ui(); - // Force a redraw, which won't happen on its own since the game isn't running - this._redraw(); + this._clear_state(); } restart_level() { this.level.restart(this.compat); + this._clear_state(); + } + + // Call after loading or restarting a level + _clear_state() { this.set_state('waiting'); + this.tic_offset = 0; + this.last_advance = 0; + this.demo_faucet = null; + this.current_keyring = {}; + this.current_toolbelt = []; + this.chips_el.classList.remove('--done'); this.time_el.classList.remove('--frozen'); this.time_el.classList.remove('--danger'); @@ -465,6 +476,7 @@ class Player extends PrimaryView { this.root.classList.remove('--bonus-visible'); this.update_ui(); + // Force a redraw, which won't happen on its own since the game isn't running this._redraw(); } @@ -597,6 +609,7 @@ class Player extends PrimaryView { else { // Rewind by undoing one tic every tic this.level.undo(); + this.update_ui(); } } this._advance_handle = window.setTimeout(this._advance_bound, 1000 / TICS_PER_SECOND); @@ -667,12 +680,30 @@ class Player extends PrimaryView { } this.message_el.textContent = this.level.hint_shown ?? ""; - this.inventory_el.textContent = ''; - // FIXME why does this stuff run when opening the /editor/ - for (let [name, count] of Object.entries(this.level.player.inventory)) { - if (count > 0) { - this.inventory_el.append(mk('img', {src: this.render_inventory_tile(name)})); + // Keys appear in a consistent order + for (let [key, nodes] of Object.entries(this.inventory_key_nodes)) { + let count = this.level.player.keyring[key] ?? 0; + if (this.current_keyring[key] === count) + continue; + + nodes.root.classList.toggle('--hidden', count <= 0); + nodes.count.classList.toggle('--hidden', count <= 1); + nodes.count.textContent = count; + + this.current_keyring[key] = count; + } + // Tools are whatever order we picked them up + for (let [i, node] of this.inventory_tool_nodes.entries()) { + let tool = this.level.player.toolbelt[i] ?? null; + if (this.current_toolbelt[i] === tool) + continue; + + node.classList.toggle('--hidden', tool === null); + if (tool) { + node.src = this.render_inventory_tile(tool); } + + this.current_toolbelt[i] = tool; } for (let action of Object.keys(ACTION_LABELS)) { @@ -689,6 +720,11 @@ class Player extends PrimaryView { } } + // waiting: haven't yet pressed a key so the timer isn't going + // playing: playing normally + // paused: um, paused + // rewinding: playing backwards + // stopped: level has ended one way or another set_state(new_state) { if (new_state === this.state) return; diff --git a/js/tiletypes.js b/js/tiletypes.js index 441bfe8..fa90ce5 100644 --- a/js/tiletypes.js +++ b/js/tiletypes.js @@ -251,7 +251,7 @@ const TILE_TYPES = { return ! (other.type.has_inventory && other.has_item('key_red')); }, on_arrive(me, level, other) { - if (other.type.has_inventory && other.take_item('key_red')) { + if (level.take_key_from_actor(other, 'key_red')) { level.transmute_tile(me, 'floor'); } }, @@ -262,7 +262,7 @@ const TILE_TYPES = { return ! (other.type.has_inventory && other.has_item('key_blue')); }, on_arrive(me, level, other) { - if (other.type.has_inventory && other.take_item('key_blue')) { + if (level.take_key_from_actor(other, 'key_blue')) { level.transmute_tile(me, 'floor'); } }, @@ -273,7 +273,7 @@ const TILE_TYPES = { return ! (other.type.has_inventory && other.has_item('key_yellow')); }, on_arrive(me, level, other) { - if (other.type.has_inventory && other.take_item('key_yellow')) { + if (level.take_key_from_actor(other, 'key_yellow')) { level.transmute_tile(me, 'floor'); } }, @@ -284,7 +284,7 @@ const TILE_TYPES = { return ! (other.type.has_inventory && other.has_item('key_green')); }, on_arrive(me, level, other) { - if (other.type.has_inventory && other.take_item('key_green')) { + if (level.take_key_from_actor(other, 'key_green')) { level.transmute_tile(me, 'floor'); } }, @@ -474,13 +474,7 @@ const TILE_TYPES = { blocks_monsters: true, blocks_blocks: true, on_arrive(me, level, other) { - if (other.inventory) { - for (let [name, count] of Object.entries(other.inventory)) { - if (count > 0 && TILE_TYPES[name].is_tool) { - other.take_item(name, count); - } - } - } + level.take_all_tools_from_actor(other); if (other.type.is_player) { level.adjust_bonus(0, 0.5); } @@ -491,13 +485,7 @@ const TILE_TYPES = { blocks_monsters: true, blocks_blocks: true, on_arrive(me, level, other) { - if (other.inventory) { - for (let [name, count] of Object.entries(other.inventory)) { - if (count > 0 && TILE_TYPES[name].is_key) { - other.take_item(name, count); - } - } - } + level.take_all_keys_from_actor(other); if (other.type.is_player) { level.adjust_bonus(0, 0.5); } diff --git a/style.css b/style.css index cc38b1f..b2f8376 100644 --- a/style.css +++ b/style.css @@ -472,20 +472,38 @@ dl.score-chart .-sum { display: none; } -.inventory { +#player .inventory { grid-area: inventory; justify-self: center; - display: flex; - flex-wrap: wrap; - align-items: start; + display: grid; + grid: auto-flow calc(var(--tile-height) * var(--scale)) / repeat(4, calc(var(--tile-width) * var(--scale))); background-size: calc(var(--tile-width) * var(--scale)) calc(var(--tile-height) * var(--scale)); width: calc(4 * var(--tile-width) * var(--scale)); min-height: calc(2 * var(--tile-height) * var(--scale)); } -.inventory img { +#player .inventory img { width: calc(var(--tile-width) * var(--scale)); } +#player .inventory .--hidden { + visibility: hidden; + pointer-events: none; +} +#player .inventory > span { + position: relative; +} +#player .inventory .-count { + font-size: calc(0.25 * var(--tile-height) * var(--scale)); + position: absolute; + top: 0; + right: 0; + padding: 0.25em 0.5em; + margin: 0.25em; /* 2px, for a 32px tileset */ + line-height: 1; + border-radius: 0.25em; + background: #0009; + color: white; +} #player .controls { grid-area: controls; display: flex;