Implement green/blue buttons, tanks, monster death; stub out remaining CC1 objects; easy restart on death
This commit is contained in:
parent
0390d54909
commit
070d276e8a
26
js/defs.js
Normal file
26
js/defs.js
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
export const DIRECTIONS = {
|
||||||
|
north: {
|
||||||
|
movement: [0, -1],
|
||||||
|
left: 'west',
|
||||||
|
right: 'east',
|
||||||
|
opposite: 'south',
|
||||||
|
},
|
||||||
|
south: {
|
||||||
|
movement: [0, 1],
|
||||||
|
left: 'east',
|
||||||
|
right: 'west',
|
||||||
|
opposite: 'north',
|
||||||
|
},
|
||||||
|
west: {
|
||||||
|
movement: [-1, 0],
|
||||||
|
left: 'south',
|
||||||
|
right: 'north',
|
||||||
|
opposite: 'east',
|
||||||
|
},
|
||||||
|
east: {
|
||||||
|
movement: [1, 0],
|
||||||
|
left: 'north',
|
||||||
|
right: 'south',
|
||||||
|
opposite: 'west',
|
||||||
|
},
|
||||||
|
};
|
||||||
78
js/main.js
78
js/main.js
@ -5,6 +5,7 @@ import * as dat from './format-dat.js';
|
|||||||
import * as format_util from './format-util.js';
|
import * as format_util from './format-util.js';
|
||||||
import TILE_TYPES from './tiletypes.js';
|
import TILE_TYPES from './tiletypes.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 { DIRECTIONS } from './defs.js';
|
||||||
|
|
||||||
function mk(tag_selector, ...children) {
|
function mk(tag_selector, ...children) {
|
||||||
let [tag, ...classes] = tag_selector.split('.');
|
let [tag, ...classes] = tag_selector.split('.');
|
||||||
@ -67,33 +68,6 @@ async function fetch(url) {
|
|||||||
|
|
||||||
const PAGE_TITLE = "Lexy's Labyrinth";
|
const PAGE_TITLE = "Lexy's Labyrinth";
|
||||||
|
|
||||||
const DIRECTIONS = {
|
|
||||||
north: {
|
|
||||||
movement: [0, -1],
|
|
||||||
left: 'west',
|
|
||||||
right: 'east',
|
|
||||||
opposite: 'south',
|
|
||||||
},
|
|
||||||
south: {
|
|
||||||
movement: [0, 1],
|
|
||||||
left: 'east',
|
|
||||||
right: 'west',
|
|
||||||
opposite: 'north',
|
|
||||||
},
|
|
||||||
west: {
|
|
||||||
movement: [-1, 0],
|
|
||||||
left: 'south',
|
|
||||||
right: 'north',
|
|
||||||
opposite: 'east',
|
|
||||||
},
|
|
||||||
east: {
|
|
||||||
movement: [1, 0],
|
|
||||||
left: 'north',
|
|
||||||
right: 'south',
|
|
||||||
opposite: 'west',
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
class Tile {
|
class Tile {
|
||||||
constructor(type, x, y, direction = 'south') {
|
constructor(type, x, y, direction = 'south') {
|
||||||
this.type = type;
|
this.type = type;
|
||||||
@ -231,16 +205,16 @@ class Level {
|
|||||||
this.width = stored_level.size_x;
|
this.width = stored_level.size_x;
|
||||||
this.height = stored_level.size_y;
|
this.height = stored_level.size_y;
|
||||||
this.restart();
|
this.restart();
|
||||||
|
}
|
||||||
|
|
||||||
|
restart() {
|
||||||
// playing: normal play
|
// playing: normal play
|
||||||
// success: has been won
|
// success: has been won
|
||||||
// failure: died
|
// failure: died
|
||||||
// note that pausing is NOT handled here, but by whatever's driving our
|
// note that pausing is NOT handled here, but by whatever's driving our
|
||||||
// event loop!
|
// event loop!
|
||||||
this.state = 'playing';
|
this.state = 'playing';
|
||||||
}
|
|
||||||
|
|
||||||
restart() {
|
|
||||||
this.cells = [];
|
this.cells = [];
|
||||||
this.player = null;
|
this.player = null;
|
||||||
this.actors = [];
|
this.actors = [];
|
||||||
@ -371,6 +345,10 @@ class Level {
|
|||||||
let d = DIRECTIONS[actor.direction];
|
let d = DIRECTIONS[actor.direction];
|
||||||
direction_preference = [actor.direction, d.opposite];
|
direction_preference = [actor.direction, d.opposite];
|
||||||
}
|
}
|
||||||
|
else if (actor.type.movement_mode === 'forward') {
|
||||||
|
// blue tank behavior: keep moving forward
|
||||||
|
direction_preference = [actor.direction];
|
||||||
|
}
|
||||||
|
|
||||||
if (! direction_preference)
|
if (! direction_preference)
|
||||||
continue;
|
continue;
|
||||||
@ -531,6 +509,13 @@ class Level {
|
|||||||
else if (tile.type.on_arrive) {
|
else if (tile.type.on_arrive) {
|
||||||
tile.type.on_arrive(tile, this, actor);
|
tile.type.on_arrive(tile, this, actor);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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!");
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -734,6 +719,29 @@ class Game {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (ev.key === ' ') {
|
||||||
|
if (this.state === 'waiting') {
|
||||||
|
// Start without moving
|
||||||
|
this.set_state('playing');
|
||||||
|
}
|
||||||
|
else if (this.state === 'stopped') {
|
||||||
|
console.log(this.level.state);
|
||||||
|
if (this.level.state === 'success') {
|
||||||
|
// Advance to the next level
|
||||||
|
// TODO game ending?
|
||||||
|
this.load_level(this.level_index + 1);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
// Restart
|
||||||
|
this.level.restart();
|
||||||
|
this.set_state('waiting');
|
||||||
|
this.update_ui();
|
||||||
|
this.redraw();
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (this.key_mapping[ev.key]) {
|
if (this.key_mapping[ev.key]) {
|
||||||
this.current_keys.add(ev.key);
|
this.current_keys.add(ev.key);
|
||||||
ev.stopPropagation();
|
ev.stopPropagation();
|
||||||
@ -813,6 +821,7 @@ class Game {
|
|||||||
}
|
}
|
||||||
|
|
||||||
load_level(level_index) {
|
load_level(level_index) {
|
||||||
|
// TODO clear out input? (when restarting, too?)
|
||||||
this.level_index = level_index;
|
this.level_index = level_index;
|
||||||
this.level = new Level(this.stored_game.levels[level_index]);
|
this.level = new Level(this.stored_game.levels[level_index]);
|
||||||
// waiting: haven't yet pressed a key so the timer isn't going
|
// waiting: haven't yet pressed a key so the timer isn't going
|
||||||
@ -1085,8 +1094,15 @@ async function main() {
|
|||||||
// TODO error handling :(
|
// TODO error handling :(
|
||||||
let stored_game;
|
let stored_game;
|
||||||
if (query.get('setpath')) {
|
if (query.get('setpath')) {
|
||||||
stored_game = new format_util.StoredGame;
|
let path = query.get('setpath');
|
||||||
stored_game.levels.push(c2m.parse_level(await fetch(query.get('setpath'))));
|
let data = await fetch(path);
|
||||||
|
if (path.match(/\.(?:dat|ccl)$/i)) {
|
||||||
|
stored_game = dat.parse_game(data);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
stored_game = new format_util.StoredGame;
|
||||||
|
stored_game.levels.push(c2m.parse_level(data));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
// TODO also support tile world's DAC when reading from local??
|
// TODO also support tile world's DAC when reading from local??
|
||||||
|
|||||||
@ -17,6 +17,8 @@ export const CC2_TILESET_LAYOUT = {
|
|||||||
cloner: [15, 1],
|
cloner: [15, 1],
|
||||||
|
|
||||||
floor: [0, 2],
|
floor: [0, 2],
|
||||||
|
wall_invisible: [0, 2],
|
||||||
|
wall_appearing: [0, 2],
|
||||||
wall: [1, 2],
|
wall: [1, 2],
|
||||||
floor_letter: [2, 2],
|
floor_letter: [2, 2],
|
||||||
'floor_letter#ascii': {
|
'floor_letter#ascii': {
|
||||||
@ -108,7 +110,14 @@ export const CC2_TILESET_LAYOUT = {
|
|||||||
|
|
||||||
green_floor: [[0, 9], [1, 9], [2, 9], [3, 9]],
|
green_floor: [[0, 9], [1, 9], [2, 9], [3, 9]],
|
||||||
purple_floor: [[4, 9], [5, 9], [6, 9], [7, 9]],
|
purple_floor: [[4, 9], [5, 9], [6, 9], [7, 9]],
|
||||||
// TODO [8, 9] is used as an overlay for green wall
|
green_wall: {
|
||||||
|
base: 'green_floor',
|
||||||
|
overlay: [8, 9],
|
||||||
|
},
|
||||||
|
purple_wall: {
|
||||||
|
base: 'purple_floor',
|
||||||
|
overlay: [8, 9],
|
||||||
|
},
|
||||||
// TODO state (10 is closed)
|
// TODO state (10 is closed)
|
||||||
trap: [9, 9],
|
trap: [9, 9],
|
||||||
button_gray: [11, 9],
|
button_gray: [11, 9],
|
||||||
@ -384,6 +393,14 @@ export class Tileset {
|
|||||||
let drawspec = this.layout[name];
|
let drawspec = this.layout[name];
|
||||||
let coords = drawspec;
|
let coords = drawspec;
|
||||||
if (! coords) console.error(name);
|
if (! coords) console.error(name);
|
||||||
|
|
||||||
|
let overlay;
|
||||||
|
if (coords.overlay) {
|
||||||
|
// Goofy overlay thing used for green/purple toggle tiles
|
||||||
|
overlay = coords.overlay;
|
||||||
|
coords = this.layout[coords.base];
|
||||||
|
}
|
||||||
|
|
||||||
if (!(coords instanceof Array)) {
|
if (!(coords instanceof Array)) {
|
||||||
// Must be an object of directions
|
// Must be an object of directions
|
||||||
coords = coords[tile.direction ?? 'south'];
|
coords = coords[tile.direction ?? 'south'];
|
||||||
@ -393,6 +410,9 @@ export class Tileset {
|
|||||||
}
|
}
|
||||||
|
|
||||||
this.blit(ctx, coords[0], coords[1], x, y);
|
this.blit(ctx, coords[0], coords[1], x, y);
|
||||||
|
if (overlay) {
|
||||||
|
this.blit(ctx, overlay[0], overlay[1], x, y);
|
||||||
|
}
|
||||||
|
|
||||||
// Special behavior for special objects
|
// Special behavior for special objects
|
||||||
// TODO? hardcode this less?
|
// TODO? hardcode this less?
|
||||||
|
|||||||
@ -1,3 +1,5 @@
|
|||||||
|
import { DIRECTIONS } from './defs.js';
|
||||||
|
|
||||||
const TILE_TYPES = {
|
const TILE_TYPES = {
|
||||||
// Floors and walls
|
// Floors and walls
|
||||||
floor: {
|
floor: {
|
||||||
@ -11,6 +13,7 @@ const TILE_TYPES = {
|
|||||||
blocks: true,
|
blocks: true,
|
||||||
},
|
},
|
||||||
wall_invisible: {
|
wall_invisible: {
|
||||||
|
// TODO cc2 seems to make these flicker briefly
|
||||||
blocks: true,
|
blocks: true,
|
||||||
},
|
},
|
||||||
wall_appearing: {
|
wall_appearing: {
|
||||||
@ -36,6 +39,9 @@ const TILE_TYPES = {
|
|||||||
thinwall_w: {
|
thinwall_w: {
|
||||||
thin_walls: new Set(['west']),
|
thin_walls: new Set(['west']),
|
||||||
},
|
},
|
||||||
|
thinwall_se: {
|
||||||
|
thin_walls: new Set(['south', 'east']),
|
||||||
|
},
|
||||||
fake_wall: {
|
fake_wall: {
|
||||||
blocks: true,
|
blocks: true,
|
||||||
on_bump(me, level, other) {
|
on_bump(me, level, other) {
|
||||||
@ -219,6 +225,11 @@ const TILE_TYPES = {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
bomb: {
|
bomb: {
|
||||||
|
// TODO explode
|
||||||
|
on_arrive(me, level, other) {
|
||||||
|
me.destroy();
|
||||||
|
other.destroy();
|
||||||
|
}
|
||||||
},
|
},
|
||||||
thief_tools: {
|
thief_tools: {
|
||||||
on_arrive(me, level, other) {
|
on_arrive(me, level, other) {
|
||||||
@ -246,20 +257,73 @@ const TILE_TYPES = {
|
|||||||
},
|
},
|
||||||
|
|
||||||
// Mechanisms
|
// Mechanisms
|
||||||
cloner: {
|
|
||||||
blocks: true,
|
|
||||||
},
|
|
||||||
dirt_block: {
|
dirt_block: {
|
||||||
blocks: true,
|
blocks: true,
|
||||||
is_object: true,
|
is_object: true,
|
||||||
is_block: true,
|
is_block: true,
|
||||||
},
|
},
|
||||||
|
green_floor: {},
|
||||||
|
green_wall: {
|
||||||
|
blocks: true,
|
||||||
|
},
|
||||||
|
cloner: {
|
||||||
|
// TODO ???
|
||||||
|
blocks: true,
|
||||||
|
},
|
||||||
|
trap: {
|
||||||
|
// TODO ???
|
||||||
|
},
|
||||||
|
teleport_blue: {
|
||||||
|
// TODO
|
||||||
|
},
|
||||||
|
// Buttons
|
||||||
|
button_blue: {
|
||||||
|
on_arrive(me, level, other) {
|
||||||
|
// Flip direction of all tanks
|
||||||
|
for (let actor of level.actors) {
|
||||||
|
// TODO generify somehow??
|
||||||
|
if (actor.type.name === 'tank_blue') {
|
||||||
|
actor.direction = DIRECTIONS[actor.direction].opposite;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
button_green: {
|
||||||
|
on_arrive(me, level, other) {
|
||||||
|
// Swap green floors and walls
|
||||||
|
for (let row of level.cells) {
|
||||||
|
for (let cell of row) {
|
||||||
|
for (let tile of cell) {
|
||||||
|
if (tile.type.name === 'green_floor') {
|
||||||
|
tile.become('green_wall');
|
||||||
|
}
|
||||||
|
else if (tile.type.name === 'green_wall') {
|
||||||
|
tile.become('green_floor');
|
||||||
|
}
|
||||||
|
else if (tile.type.name === 'green_chip') {
|
||||||
|
tile.become('green_bomb');
|
||||||
|
}
|
||||||
|
else if (tile.type.name === 'green_bomb') {
|
||||||
|
tile.become('green_chip');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
button_brown: {
|
||||||
|
// TODO how do i implement this.
|
||||||
|
},
|
||||||
|
button_red: {
|
||||||
|
// TODO
|
||||||
|
},
|
||||||
|
|
||||||
// Critters
|
// Critters
|
||||||
bug: {
|
bug: {
|
||||||
is_actor: true,
|
is_actor: true,
|
||||||
is_object: true,
|
is_object: true,
|
||||||
is_monster: true,
|
is_monster: true,
|
||||||
|
blocks_monsters: true,
|
||||||
movement_mode: 'follow-left',
|
movement_mode: 'follow-left',
|
||||||
movement_speed: 4,
|
movement_speed: 4,
|
||||||
},
|
},
|
||||||
@ -267,6 +331,7 @@ const TILE_TYPES = {
|
|||||||
is_actor: true,
|
is_actor: true,
|
||||||
is_object: true,
|
is_object: true,
|
||||||
is_monster: true,
|
is_monster: true,
|
||||||
|
blocks_monsters: true,
|
||||||
movement_mode: 'follow-right',
|
movement_mode: 'follow-right',
|
||||||
movement_speed: 4,
|
movement_speed: 4,
|
||||||
},
|
},
|
||||||
@ -274,25 +339,45 @@ const TILE_TYPES = {
|
|||||||
is_actor: true,
|
is_actor: true,
|
||||||
is_object: true,
|
is_object: true,
|
||||||
is_monster: true,
|
is_monster: true,
|
||||||
|
blocks_monsters: true,
|
||||||
movement_mode: 'bounce',
|
movement_mode: 'bounce',
|
||||||
movement_speed: 4,
|
movement_speed: 4,
|
||||||
},
|
},
|
||||||
|
walker: {
|
||||||
|
is_actor: true,
|
||||||
|
is_object: true,
|
||||||
|
is_monster: true,
|
||||||
|
blocks_monsters: true,
|
||||||
|
// TODO movement_move: 'bounce-random',
|
||||||
|
movement_speed: 4,
|
||||||
|
},
|
||||||
|
tank_blue: {
|
||||||
|
is_actor: true,
|
||||||
|
is_object: true,
|
||||||
|
is_monster: true,
|
||||||
|
blocks_monsters: true,
|
||||||
|
movement_mode: 'forward',
|
||||||
|
movement_speed: 4,
|
||||||
|
},
|
||||||
blob: {
|
blob: {
|
||||||
is_actor: true,
|
is_actor: true,
|
||||||
is_object: true,
|
is_object: true,
|
||||||
is_monster: true,
|
is_monster: true,
|
||||||
|
blocks_monsters: true,
|
||||||
movement_speed: 8,
|
movement_speed: 8,
|
||||||
},
|
},
|
||||||
teeth: {
|
teeth: {
|
||||||
is_actor: true,
|
is_actor: true,
|
||||||
is_object: true,
|
is_object: true,
|
||||||
is_monster: true,
|
is_monster: true,
|
||||||
|
blocks_monsters: true,
|
||||||
movement_speed: 4,
|
movement_speed: 4,
|
||||||
},
|
},
|
||||||
fireball: {
|
fireball: {
|
||||||
is_actor: true,
|
is_actor: true,
|
||||||
is_object: true,
|
is_object: true,
|
||||||
is_monster: true,
|
is_monster: true,
|
||||||
|
blocks_monsters: true,
|
||||||
movement_mode: 'turn-right',
|
movement_mode: 'turn-right',
|
||||||
movement_speed: 4,
|
movement_speed: 4,
|
||||||
ignores: new Set(['fire']),
|
ignores: new Set(['fire']),
|
||||||
@ -301,6 +386,7 @@ const TILE_TYPES = {
|
|||||||
is_actor: true,
|
is_actor: true,
|
||||||
is_object: true,
|
is_object: true,
|
||||||
is_monster: true,
|
is_monster: true,
|
||||||
|
blocks_monsters: true,
|
||||||
movement_mode: 'turn-left',
|
movement_mode: 'turn-left',
|
||||||
movement_speed: 4,
|
movement_speed: 4,
|
||||||
ignores: new Set(['water']),
|
ignores: new Set(['water']),
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user