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 id="player" hidden>
|
||||
<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="chips">
|
||||
<h3>Chips</h3>
|
||||
|
||||
33
js/game.js
33
js/game.js
@ -622,7 +622,7 @@ export class Level {
|
||||
|
||||
this.time_remaining -= 1;
|
||||
if (this.time_remaining <= 0) {
|
||||
this.fail("Time's up!");
|
||||
this.fail('time');
|
||||
}
|
||||
}
|
||||
else {
|
||||
@ -767,31 +767,28 @@ export class Level {
|
||||
}
|
||||
|
||||
// Check for a couple effects that always apply immediately
|
||||
// TODO i guess this covers blocks too
|
||||
// TODO do blocks smash monsters?
|
||||
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)) {
|
||||
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
|
||||
// TODO this only works because i have the player move first; in lynx the check is the other
|
||||
// way around
|
||||
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) {
|
||||
@ -931,7 +928,7 @@ export class Level {
|
||||
this.time_remaining = 1;
|
||||
}
|
||||
else {
|
||||
this.fail("Time's up!");
|
||||
this.fail('time');
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -939,10 +936,10 @@ export class Level {
|
||||
fail(message) {
|
||||
this.pending_undo.push(() => {
|
||||
this.state = 'playing';
|
||||
this.fail_message = null;
|
||||
this.fail_reason = null;
|
||||
});
|
||||
this.state = 'failure';
|
||||
this.fail_message = message;
|
||||
this.fail_reason = message;
|
||||
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 { Tileset, CC2_TILESET_LAYOUT, TILE_WORLD_TILESET_LAYOUT } from './tileset.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";
|
||||
// Stackable modal overlay of some kind, usually a dialog
|
||||
@ -136,6 +136,65 @@ const ACTION_DIRECTIONS = {
|
||||
left: 'west',
|
||||
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 {
|
||||
constructor(conductor) {
|
||||
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-height', `${this.conductor.tileset.size_y}px`);
|
||||
this.level_el = this.root.querySelector('.level');
|
||||
this.overlay_message_el = this.root.querySelector('.overlay-message');
|
||||
this.message_el = this.root.querySelector('.message');
|
||||
this.chips_el = this.root.querySelector('.chips output');
|
||||
this.time_el = this.root.querySelector('.time output');
|
||||
this.bonus_el = this.root.querySelector('.bonus output');
|
||||
this.inventory_el = this.root.querySelector('.inventory');
|
||||
this.bummer_el = this.root.querySelector('.bummer');
|
||||
this.input_el = this.root.querySelector('.input');
|
||||
this.demo_el = this.root.querySelector('.demo');
|
||||
|
||||
@ -589,26 +648,67 @@ class Player extends PrimaryView {
|
||||
|
||||
this.state = new_state;
|
||||
|
||||
// Populate the overlay
|
||||
let overlay_reason = '';
|
||||
let overlay_top = '';
|
||||
let overlay_middle = null;
|
||||
let overlay_bottom = '';
|
||||
if (this.state === 'waiting') {
|
||||
this.bummer_el.textContent = "Ready!";
|
||||
}
|
||||
else if (this.state === 'playing' || this.state === 'rewinding') {
|
||||
this.bummer_el.textContent = "";
|
||||
overlay_reason = 'waiting';
|
||||
overlay_middle = "Ready!";
|
||||
}
|
||||
else if (this.state === 'paused') {
|
||||
this.bummer_el.textContent = "/// paused ///";
|
||||
overlay_reason = 'paused';
|
||||
overlay_bottom = "/// paused ///";
|
||||
}
|
||||
else if (this.state === 'stopped') {
|
||||
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 {
|
||||
this.bummer_el.textContent = "";
|
||||
overlay_reason = 'success';
|
||||
let base = (this.conductor.level_index + 1) * 500;
|
||||
let time = Math.ceil((this.level.time_remaining ?? 0) / 20) * 10;
|
||||
this.bummer_el.append(
|
||||
mk('p', "go bit buster!"),
|
||||
mk('dl.score-chart',
|
||||
// Pick a success message
|
||||
// TODO done on first try; took many tries
|
||||
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('dd', base),
|
||||
mk('dt', "time bonus"),
|
||||
@ -621,10 +721,17 @@ class Player extends PrimaryView {
|
||||
mk('dd', "(TODO)"),
|
||||
mk('dt', "total score"),
|
||||
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
|
||||
// themselves if the game isn't running, so restart them here
|
||||
|
||||
@ -291,7 +291,7 @@ const TILE_TYPES = {
|
||||
}
|
||||
else if (other.type.is_player) {
|
||||
level.transmute_tile(other, 'player_burned');
|
||||
level.fail("Oops! You can't walk on fire without fire boots!");
|
||||
level.fail('burned');
|
||||
}
|
||||
else {
|
||||
level.remove_tile(other);
|
||||
@ -312,7 +312,7 @@ const TILE_TYPES = {
|
||||
}
|
||||
else if (other.type.is_player) {
|
||||
level.transmute_tile(other, 'splash');
|
||||
level.fail("swimming with the fishes");
|
||||
level.fail('drowned');
|
||||
}
|
||||
else {
|
||||
level.transmute_tile(other, 'splash');
|
||||
@ -436,7 +436,7 @@ const TILE_TYPES = {
|
||||
let was_player = other.type.is_player;
|
||||
level.transmute_tile(other, 'explosion');
|
||||
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;
|
||||
level.transmute_tile(other, 'explosion');
|
||||
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) {
|
||||
let [tag, ...classes] = tag_selector.split('.');
|
||||
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-height: 9;
|
||||
}
|
||||
.bummer {
|
||||
#player .overlay-message {
|
||||
grid-area: level;
|
||||
place-self: stretch;
|
||||
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
display: grid;
|
||||
grid-template-rows: 1fr 3fr 1fr;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
|
||||
z-index: 1;
|
||||
font-size: 48px;
|
||||
padding: 10%;
|
||||
font-size: calc(0.5 * var(--tile-width) * var(--scale));
|
||||
padding: 6.25%;
|
||||
background: #0009;
|
||||
color: white;
|
||||
text-align: center;
|
||||
font-weight: bold;
|
||||
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;
|
||||
}
|
||||
.bummer p {
|
||||
margin: 0;
|
||||
#player .overlay-message[data-reason=failure] {
|
||||
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 {
|
||||
display: grid;
|
||||
grid-auto-columns: 1fr 1fr;
|
||||
font-size: 1.25rem;
|
||||
margin: auto;
|
||||
font-weight: normal;
|
||||
}
|
||||
dl.score-chart dt {
|
||||
|
||||
Loading…
Reference in New Issue
Block a user