Add obituaries, and get them out of the game itself
This commit is contained in:
parent
fe12c599bc
commit
57810da581
@ -52,7 +52,11 @@
|
|||||||
</main>
|
</main>
|
||||||
<main id="player" hidden>
|
<main id="player" hidden>
|
||||||
<div class="level"><!-- level canvas and any overlays go here --></div>
|
<div class="level"><!-- level canvas and any overlays go here --></div>
|
||||||
<div class="bummer"></div>
|
<div class="overlay-message">
|
||||||
|
<h1 class="-top"></h1>
|
||||||
|
<div class="-middle"></div>
|
||||||
|
<p class="-bottom"></p>
|
||||||
|
</div>
|
||||||
<div class="message"></div>
|
<div class="message"></div>
|
||||||
<div class="chips">
|
<div class="chips">
|
||||||
<h3>Chips</h3>
|
<h3>Chips</h3>
|
||||||
|
|||||||
33
js/game.js
33
js/game.js
@ -622,7 +622,7 @@ export class Level {
|
|||||||
|
|
||||||
this.time_remaining -= 1;
|
this.time_remaining -= 1;
|
||||||
if (this.time_remaining <= 0) {
|
if (this.time_remaining <= 0) {
|
||||||
this.fail("Time's up!");
|
this.fail('time');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
@ -767,31 +767,28 @@ export class Level {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Check for a couple effects that always apply immediately
|
// Check for a couple effects that always apply immediately
|
||||||
// TODO i guess this covers blocks too
|
|
||||||
// TODO do blocks smash monsters?
|
// TODO do blocks smash monsters?
|
||||||
for (let tile of goal_cell) {
|
for (let tile of goal_cell) {
|
||||||
|
if (actor.type.is_player && tile.type.is_monster) {
|
||||||
|
this.fail(tile.type.name);
|
||||||
|
}
|
||||||
|
else if (actor.type.is_monster && tile.type.is_player) {
|
||||||
|
this.fail(actor.type.name);
|
||||||
|
}
|
||||||
|
else if (actor.type.is_block && tile.type.is_player) {
|
||||||
|
this.fail('squished');
|
||||||
|
}
|
||||||
|
|
||||||
if (tile.type.slide_mode && ! actor.ignores(tile.type.name)) {
|
if (tile.type.slide_mode && ! actor.ignores(tile.type.name)) {
|
||||||
this.make_slide(actor, tile.type.slide_mode);
|
this.make_slide(actor, tile.type.slide_mode);
|
||||||
}
|
}
|
||||||
if ((actor.type.is_player && tile.type.is_monster) ||
|
|
||||||
(actor.type.is_monster && tile.type.is_player))
|
|
||||||
{
|
|
||||||
// TODO ooh, obituaries
|
|
||||||
this.fail("Oops! Watch out for creatures!");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (actor.type.is_block && tile.type.is_player) {
|
|
||||||
// TODO ooh, obituaries
|
|
||||||
this.fail("squish");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// If we're stepping directly on the player, that kills them too
|
// If we're stepping directly on the player, that kills them too
|
||||||
// TODO this only works because i have the player move first; in lynx the check is the other
|
// TODO this only works because i have the player move first; in lynx the check is the other
|
||||||
// way around
|
// way around
|
||||||
if (actor.type.is_monster && goal_cell === this.player_leaving_cell) {
|
if (actor.type.is_monster && goal_cell === this.player_leaving_cell) {
|
||||||
this.fail("Oops! Watch out for creatures!");
|
this.fail(actor.type.name);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.compat.tiles_react_instantly) {
|
if (this.compat.tiles_react_instantly) {
|
||||||
@ -931,7 +928,7 @@ export class Level {
|
|||||||
this.time_remaining = 1;
|
this.time_remaining = 1;
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
this.fail("Time's up!");
|
this.fail('time');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -939,10 +936,10 @@ export class Level {
|
|||||||
fail(message) {
|
fail(message) {
|
||||||
this.pending_undo.push(() => {
|
this.pending_undo.push(() => {
|
||||||
this.state = 'playing';
|
this.state = 'playing';
|
||||||
this.fail_message = null;
|
this.fail_reason = null;
|
||||||
});
|
});
|
||||||
this.state = 'failure';
|
this.state = 'failure';
|
||||||
this.fail_message = message;
|
this.fail_reason = message;
|
||||||
throw new GameEnded;
|
throw new GameEnded;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
133
js/main.js
133
js/main.js
@ -8,7 +8,7 @@ import { Level } from './game.js';
|
|||||||
import CanvasRenderer from './renderer-canvas.js';
|
import CanvasRenderer from './renderer-canvas.js';
|
||||||
import { Tileset, CC2_TILESET_LAYOUT, TILE_WORLD_TILESET_LAYOUT } from './tileset.js';
|
import { Tileset, CC2_TILESET_LAYOUT, TILE_WORLD_TILESET_LAYOUT } from './tileset.js';
|
||||||
import TILE_TYPES from './tiletypes.js';
|
import TILE_TYPES from './tiletypes.js';
|
||||||
import { mk, promise_event, fetch, walk_grid } from './util.js';
|
import { random_choice, mk, promise_event, fetch, walk_grid } from './util.js';
|
||||||
|
|
||||||
const PAGE_TITLE = "Lexy's Labyrinth";
|
const PAGE_TITLE = "Lexy's Labyrinth";
|
||||||
// Stackable modal overlay of some kind, usually a dialog
|
// Stackable modal overlay of some kind, usually a dialog
|
||||||
@ -136,6 +136,65 @@ const ACTION_DIRECTIONS = {
|
|||||||
left: 'west',
|
left: 'west',
|
||||||
right: 'east',
|
right: 'east',
|
||||||
};
|
};
|
||||||
|
const OBITUARIES = {
|
||||||
|
drowned: [
|
||||||
|
"player tried out water cooling",
|
||||||
|
"player fell into the c",
|
||||||
|
],
|
||||||
|
burned: [
|
||||||
|
"player's core temp got too high",
|
||||||
|
"player's plans went up in smoke",
|
||||||
|
],
|
||||||
|
exploded: [
|
||||||
|
"player didn't watch where they step",
|
||||||
|
"player is having a blast",
|
||||||
|
"player tripped over something of mine",
|
||||||
|
],
|
||||||
|
squished: [
|
||||||
|
"player was overwhelmed by a block of ram",
|
||||||
|
"player became two-dimensional",
|
||||||
|
],
|
||||||
|
time: [
|
||||||
|
"player tried to overclock",
|
||||||
|
"player's time ran out",
|
||||||
|
"player's speedrun went badly",
|
||||||
|
],
|
||||||
|
generic: [
|
||||||
|
"player had a bad time",
|
||||||
|
],
|
||||||
|
|
||||||
|
// Specific creatures
|
||||||
|
ball: [
|
||||||
|
"player is having a ball",
|
||||||
|
],
|
||||||
|
walker: [
|
||||||
|
"player let it walk all over them",
|
||||||
|
],
|
||||||
|
fireball: [
|
||||||
|
"player had a meltdown",
|
||||||
|
],
|
||||||
|
glider: [
|
||||||
|
"player's ship came in",
|
||||||
|
],
|
||||||
|
tank_blue: [
|
||||||
|
"player didn't watch where they tread",
|
||||||
|
],
|
||||||
|
tank_yellow: [
|
||||||
|
"player let things get out of control",
|
||||||
|
],
|
||||||
|
bug: [
|
||||||
|
"player has ants in their pants",
|
||||||
|
],
|
||||||
|
paramecium: [
|
||||||
|
"player has the creepy crawlies",
|
||||||
|
],
|
||||||
|
teeth: [
|
||||||
|
"player got a mega bite",
|
||||||
|
],
|
||||||
|
blob: [
|
||||||
|
"player didn't do a gooed job",
|
||||||
|
],
|
||||||
|
};
|
||||||
class Player extends PrimaryView {
|
class Player extends PrimaryView {
|
||||||
constructor(conductor) {
|
constructor(conductor) {
|
||||||
super(conductor, document.body.querySelector('main#player'));
|
super(conductor, document.body.querySelector('main#player'));
|
||||||
@ -163,12 +222,12 @@ class Player extends PrimaryView {
|
|||||||
this.root.style.setProperty('--tile-width', `${this.conductor.tileset.size_x}px`);
|
this.root.style.setProperty('--tile-width', `${this.conductor.tileset.size_x}px`);
|
||||||
this.root.style.setProperty('--tile-height', `${this.conductor.tileset.size_y}px`);
|
this.root.style.setProperty('--tile-height', `${this.conductor.tileset.size_y}px`);
|
||||||
this.level_el = this.root.querySelector('.level');
|
this.level_el = this.root.querySelector('.level');
|
||||||
|
this.overlay_message_el = this.root.querySelector('.overlay-message');
|
||||||
this.message_el = this.root.querySelector('.message');
|
this.message_el = this.root.querySelector('.message');
|
||||||
this.chips_el = this.root.querySelector('.chips output');
|
this.chips_el = this.root.querySelector('.chips output');
|
||||||
this.time_el = this.root.querySelector('.time output');
|
this.time_el = this.root.querySelector('.time output');
|
||||||
this.bonus_el = this.root.querySelector('.bonus output');
|
this.bonus_el = this.root.querySelector('.bonus output');
|
||||||
this.inventory_el = this.root.querySelector('.inventory');
|
this.inventory_el = this.root.querySelector('.inventory');
|
||||||
this.bummer_el = this.root.querySelector('.bummer');
|
|
||||||
this.input_el = this.root.querySelector('.input');
|
this.input_el = this.root.querySelector('.input');
|
||||||
this.demo_el = this.root.querySelector('.demo');
|
this.demo_el = this.root.querySelector('.demo');
|
||||||
|
|
||||||
@ -589,26 +648,67 @@ class Player extends PrimaryView {
|
|||||||
|
|
||||||
this.state = new_state;
|
this.state = new_state;
|
||||||
|
|
||||||
|
// Populate the overlay
|
||||||
|
let overlay_reason = '';
|
||||||
|
let overlay_top = '';
|
||||||
|
let overlay_middle = null;
|
||||||
|
let overlay_bottom = '';
|
||||||
if (this.state === 'waiting') {
|
if (this.state === 'waiting') {
|
||||||
this.bummer_el.textContent = "Ready!";
|
overlay_reason = 'waiting';
|
||||||
}
|
overlay_middle = "Ready!";
|
||||||
else if (this.state === 'playing' || this.state === 'rewinding') {
|
|
||||||
this.bummer_el.textContent = "";
|
|
||||||
}
|
}
|
||||||
else if (this.state === 'paused') {
|
else if (this.state === 'paused') {
|
||||||
this.bummer_el.textContent = "/// paused ///";
|
overlay_reason = 'paused';
|
||||||
|
overlay_bottom = "/// paused ///";
|
||||||
}
|
}
|
||||||
else if (this.state === 'stopped') {
|
else if (this.state === 'stopped') {
|
||||||
if (this.level.state === 'failure') {
|
if (this.level.state === 'failure') {
|
||||||
this.bummer_el.textContent = this.level.fail_message;
|
overlay_reason = 'failure';
|
||||||
|
overlay_top = "whoops";
|
||||||
|
let obits = OBITUARIES[this.level.fail_reason] ?? OBITUARIES['generic'];
|
||||||
|
overlay_bottom = random_choice(obits);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
this.bummer_el.textContent = "";
|
overlay_reason = 'success';
|
||||||
let base = (this.conductor.level_index + 1) * 500;
|
let base = (this.conductor.level_index + 1) * 500;
|
||||||
let time = Math.ceil((this.level.time_remaining ?? 0) / 20) * 10;
|
let time = Math.ceil((this.level.time_remaining ?? 0) / 20) * 10;
|
||||||
this.bummer_el.append(
|
// Pick a success message
|
||||||
mk('p', "go bit buster!"),
|
// TODO done on first try; took many tries
|
||||||
mk('dl.score-chart',
|
let time_left_fraction = null;
|
||||||
|
if (this.level.time_remaining !== null && this.level.stored_level.time_limit !== null) {
|
||||||
|
time_left_fraction = this.level.time_remaining / 20 / this.level.stored_level.time_limit;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.level.chips_remaining > 0) {
|
||||||
|
overlay_top = random_choice([
|
||||||
|
"socket to em!", "go bug blaster!",
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
else if (this.level.time_remaining && this.level.time_remaining < 200) {
|
||||||
|
overlay_top = random_choice([
|
||||||
|
"in the nick of time!", "cutting it close!",
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
else if (time_left_fraction !== null && time_left_fraction > 1) {
|
||||||
|
overlay_top = random_choice([
|
||||||
|
"faster than light!", "impossible speed!", "pipelined!",
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
else if (time_left_fraction !== null && time_left_fraction > 0.75) {
|
||||||
|
overlay_top = random_choice([
|
||||||
|
"lightning quick!", "nice speedrun!", "eagerly evaluated!",
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
overlay_top = random_choice([
|
||||||
|
"you did it!", "nice going!", "great job!", "good work!",
|
||||||
|
"onwards!", "tubular!", "yeehaw!", "hot damn!",
|
||||||
|
"alphanumeric!", "nice dynamic typing!",
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
overlay_bottom = "press spacebar to continue";
|
||||||
|
|
||||||
|
overlay_middle = mk('dl.score-chart',
|
||||||
mk('dt', "base score"),
|
mk('dt', "base score"),
|
||||||
mk('dd', base),
|
mk('dd', base),
|
||||||
mk('dt', "time bonus"),
|
mk('dt', "time bonus"),
|
||||||
@ -621,10 +721,17 @@ class Player extends PrimaryView {
|
|||||||
mk('dd', "(TODO)"),
|
mk('dd', "(TODO)"),
|
||||||
mk('dt', "total score"),
|
mk('dt', "total score"),
|
||||||
mk('dd', "(TODO)"),
|
mk('dd', "(TODO)"),
|
||||||
),
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
this.overlay_message_el.setAttribute('data-reason', overlay_reason);
|
||||||
|
this.overlay_message_el.querySelector('.-top').textContent = overlay_top;
|
||||||
|
this.overlay_message_el.querySelector('.-bottom').textContent = overlay_bottom;
|
||||||
|
let middle = this.overlay_message_el.querySelector('.-middle');
|
||||||
|
middle.textContent = '';
|
||||||
|
if (overlay_middle) {
|
||||||
|
middle.append(overlay_middle);
|
||||||
|
}
|
||||||
|
|
||||||
// The advance and redraw methods run in a loop, but they cancel
|
// The advance and redraw methods run in a loop, but they cancel
|
||||||
// themselves if the game isn't running, so restart them here
|
// themselves if the game isn't running, so restart them here
|
||||||
|
|||||||
@ -291,7 +291,7 @@ const TILE_TYPES = {
|
|||||||
}
|
}
|
||||||
else if (other.type.is_player) {
|
else if (other.type.is_player) {
|
||||||
level.transmute_tile(other, 'player_burned');
|
level.transmute_tile(other, 'player_burned');
|
||||||
level.fail("Oops! You can't walk on fire without fire boots!");
|
level.fail('burned');
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
level.remove_tile(other);
|
level.remove_tile(other);
|
||||||
@ -312,7 +312,7 @@ const TILE_TYPES = {
|
|||||||
}
|
}
|
||||||
else if (other.type.is_player) {
|
else if (other.type.is_player) {
|
||||||
level.transmute_tile(other, 'splash');
|
level.transmute_tile(other, 'splash');
|
||||||
level.fail("swimming with the fishes");
|
level.fail('drowned');
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
level.transmute_tile(other, 'splash');
|
level.transmute_tile(other, 'splash');
|
||||||
@ -436,7 +436,7 @@ const TILE_TYPES = {
|
|||||||
let was_player = other.type.is_player;
|
let was_player = other.type.is_player;
|
||||||
level.transmute_tile(other, 'explosion');
|
level.transmute_tile(other, 'explosion');
|
||||||
if (was_player) {
|
if (was_player) {
|
||||||
level.fail("watch where you step");
|
level.fail('exploded');
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -555,7 +555,7 @@ const TILE_TYPES = {
|
|||||||
let was_player = other.type.is_player;
|
let was_player = other.type.is_player;
|
||||||
level.transmute_tile(other, 'explosion');
|
level.transmute_tile(other, 'explosion');
|
||||||
if (was_player) {
|
if (was_player) {
|
||||||
level.fail("watch where you step");
|
level.fail('exploded');
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|||||||
@ -1,3 +1,7 @@
|
|||||||
|
export function random_choice(list) {
|
||||||
|
return list[Math.floor(Math.random() * list.length)];
|
||||||
|
}
|
||||||
|
|
||||||
export function mk(tag_selector, ...children) {
|
export function mk(tag_selector, ...children) {
|
||||||
let [tag, ...classes] = tag_selector.split('.');
|
let [tag, ...classes] = tag_selector.split('.');
|
||||||
let el = document.createElement(tag);
|
let el = document.createElement(tag);
|
||||||
|
|||||||
32
style.css
32
style.css
@ -323,34 +323,46 @@ body[data-mode=player] #editor-play {
|
|||||||
--viewport-width: 9;
|
--viewport-width: 9;
|
||||||
--viewport-height: 9;
|
--viewport-height: 9;
|
||||||
}
|
}
|
||||||
.bummer {
|
#player .overlay-message {
|
||||||
grid-area: level;
|
grid-area: level;
|
||||||
place-self: stretch;
|
place-self: stretch;
|
||||||
|
|
||||||
display: flex;
|
display: grid;
|
||||||
flex-direction: column;
|
grid-template-rows: 1fr 3fr 1fr;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
|
||||||
z-index: 1;
|
z-index: 1;
|
||||||
font-size: 48px;
|
font-size: calc(0.5 * var(--tile-width) * var(--scale));
|
||||||
padding: 10%;
|
padding: 6.25%;
|
||||||
background: #0009;
|
background: #0009;
|
||||||
color: white;
|
color: white;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
font-weight: bold;
|
|
||||||
text-shadow: 0 2px 1px black;
|
text-shadow: 0 2px 1px black;
|
||||||
}
|
}
|
||||||
.bummer:empty {
|
#player .overlay-message p {
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
#player .overlay-message .-top {
|
||||||
|
font-size: 1.5em;
|
||||||
|
}
|
||||||
|
#player .overlay-message .-middle {
|
||||||
|
}
|
||||||
|
#player .overlay-message .-bottom {
|
||||||
|
}
|
||||||
|
#player .overlay-message[data-reason=""] {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
.bummer p {
|
#player .overlay-message[data-reason=failure] {
|
||||||
margin: 0;
|
box-shadow: inset 0 0 calc(4 * var(--tile-width)) var(--tile-width) black;
|
||||||
|
}
|
||||||
|
#player .overlay-message[data-reason=success] {
|
||||||
|
box-shadow: inset 0 0 var(--tile-width) white;
|
||||||
}
|
}
|
||||||
dl.score-chart {
|
dl.score-chart {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-auto-columns: 1fr 1fr;
|
grid-auto-columns: 1fr 1fr;
|
||||||
font-size: 1.25rem;
|
margin: auto;
|
||||||
font-weight: normal;
|
font-weight: normal;
|
||||||
}
|
}
|
||||||
dl.score-chart dt {
|
dl.score-chart dt {
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user