Add a basic implementation of doppelgangers
This commit is contained in:
parent
f0680ce0c4
commit
8d197ce479
@ -496,13 +496,11 @@ const TILE_ENCODING = {
|
|||||||
name: 'doppelganger1',
|
name: 'doppelganger1',
|
||||||
has_next: true,
|
has_next: true,
|
||||||
extra_args: [arg_direction],
|
extra_args: [arg_direction],
|
||||||
error: "Doppelganger Lexy is not yet implemented, sorry!",
|
|
||||||
},
|
},
|
||||||
0x66: {
|
0x66: {
|
||||||
name: 'doppelganger2',
|
name: 'doppelganger2',
|
||||||
has_next: true,
|
has_next: true,
|
||||||
extra_args: [arg_direction],
|
extra_args: [arg_direction],
|
||||||
error: "Doppelganger Cerise is not yet implemented, sorry!",
|
|
||||||
},
|
},
|
||||||
0x68: {
|
0x68: {
|
||||||
name: 'bowling_ball',
|
name: 'bowling_ball',
|
||||||
|
|||||||
56
js/game.js
56
js/game.js
@ -48,10 +48,10 @@ export class Tile {
|
|||||||
// the name is the same?
|
// the name is the same?
|
||||||
blocks(other, direction, level) {
|
blocks(other, direction, level) {
|
||||||
// Extremely awkward special case: items don't block monsters if the cell also contains an
|
// Extremely awkward special case: items don't block monsters if the cell also contains an
|
||||||
// item modifier (i.e. "no" sign) or a player
|
// item modifier (i.e. "no" sign) or a real player
|
||||||
// TODO would love to get this outta here
|
// TODO would love to get this outta here
|
||||||
if (this.type.is_item &&
|
if (this.type.is_item &&
|
||||||
this.cell.some(tile => tile.type.item_modifier || tile.type.is_player))
|
this.cell.some(tile => tile.type.item_modifier || tile.type.is_real_player))
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
if (this.type.blocks_collision & other.type.collision_mask)
|
if (this.type.blocks_collision & other.type.collision_mask)
|
||||||
@ -281,6 +281,7 @@ export class Level {
|
|||||||
let n = 0;
|
let n = 0;
|
||||||
let connectables = [];
|
let connectables = [];
|
||||||
this.power_sources = [];
|
this.power_sources = [];
|
||||||
|
this.players = [];
|
||||||
// FIXME handle traps correctly:
|
// FIXME handle traps correctly:
|
||||||
// - if an actor is in the cell, set the trap to open and unstick everything in it
|
// - if an actor is in the cell, set the trap to open and unstick everything in it
|
||||||
for (let y = 0; y < this.height; y++) {
|
for (let y = 0; y < this.height; y++) {
|
||||||
@ -304,10 +305,8 @@ export class Level {
|
|||||||
this.power_sources.push(tile);
|
this.power_sources.push(tile);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (tile.type.is_player) {
|
if (tile.type.is_real_player) {
|
||||||
// TODO handle multiple players, also chip and melinda both
|
this.players.push(tile);
|
||||||
// TODO complain if no player
|
|
||||||
this.player = tile;
|
|
||||||
}
|
}
|
||||||
if (tile.type.is_actor) {
|
if (tile.type.is_actor) {
|
||||||
this.actors.push(tile);
|
this.actors.push(tile);
|
||||||
@ -320,6 +319,12 @@ export class Level {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// TODO complain if no player
|
||||||
|
this.player = this.players[0];
|
||||||
|
this.player_index = 0;
|
||||||
|
// Used for doppelgangers
|
||||||
|
this.player1_move = null;
|
||||||
|
this.player2_move = null;
|
||||||
|
|
||||||
// Connect buttons and teleporters
|
// Connect buttons and teleporters
|
||||||
let num_cells = this.width * this.height;
|
let num_cells = this.width * this.height;
|
||||||
@ -485,7 +490,8 @@ export class Level {
|
|||||||
for (let key of [
|
for (let key of [
|
||||||
'_rng1', '_rng2', '_blob_modifier', 'force_floor_direction',
|
'_rng1', '_rng2', '_blob_modifier', 'force_floor_direction',
|
||||||
'tic_counter', 'time_remaining', 'timer_paused',
|
'tic_counter', 'time_remaining', 'timer_paused',
|
||||||
'chips_remaining', 'bonus_points', 'hint_shown', 'state'
|
'chips_remaining', 'bonus_points', 'hint_shown', 'state',
|
||||||
|
'player1_move', 'player2_move',
|
||||||
]) {
|
]) {
|
||||||
this.pending_undo.level_props[key] = this[key];
|
this.pending_undo.level_props[key] = this[key];
|
||||||
}
|
}
|
||||||
@ -608,6 +614,7 @@ export class Level {
|
|||||||
// XXX this in particular has some subtleties in lynx (e.g. you
|
// XXX this in particular has some subtleties in lynx (e.g. you
|
||||||
// can override forwards??) and DEFINITELY all kinds of stuff
|
// can override forwards??) and DEFINITELY all kinds of stuff
|
||||||
// in ms
|
// in ms
|
||||||
|
// XXX unclear what impact this has on doppelgangers
|
||||||
if (actor === this.player &&
|
if (actor === this.player &&
|
||||||
p1_primary_direction &&
|
p1_primary_direction &&
|
||||||
actor.last_move_was_force)
|
actor.last_move_was_force)
|
||||||
@ -624,6 +631,15 @@ export class Level {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
else if (actor === this.player) {
|
else if (actor === this.player) {
|
||||||
|
// Sorry for the confusion; "p1" and "p2" in the direction args refer to physical
|
||||||
|
// human players, NOT to the two types of player tiles!
|
||||||
|
if (this.player.type.name === 'player') {
|
||||||
|
this.player1_move = p1_primary_direction;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
this.player2_move = p1_primary_direction;
|
||||||
|
}
|
||||||
|
|
||||||
if (p1_primary_direction) {
|
if (p1_primary_direction) {
|
||||||
actor.decision = p1_primary_direction;
|
actor.decision = p1_primary_direction;
|
||||||
this._set_tile_prop(actor, 'last_move_was_force', false);
|
this._set_tile_prop(actor, 'last_move_was_force', false);
|
||||||
@ -637,6 +653,11 @@ export class Level {
|
|||||||
// go back into the trap. this is consistent with CC2 but not ms/lynx
|
// go back into the trap. this is consistent with CC2 but not ms/lynx
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
// FIXME seems like all this could be moved into a tile type function since it's all
|
||||||
|
// only used basically one time each
|
||||||
|
else if (actor.type.decide_movement) {
|
||||||
|
direction_preference = actor.type.decide_movement(actor, this);
|
||||||
|
}
|
||||||
else if (actor.type.movement_mode === 'forward') {
|
else if (actor.type.movement_mode === 'forward') {
|
||||||
// blue tank behavior: keep moving forward, reverse if the flag is set
|
// blue tank behavior: keep moving forward, reverse if the flag is set
|
||||||
let direction = actor.direction;
|
let direction = actor.direction;
|
||||||
@ -791,7 +812,7 @@ export class Level {
|
|||||||
|
|
||||||
// Players can also bump the tiles in the cell next to the one they're leaving
|
// Players can also bump the tiles in the cell next to the one they're leaving
|
||||||
let dir2 = actor.secondary_direction;
|
let dir2 = actor.secondary_direction;
|
||||||
if (actor.type.is_player && dir2 &&
|
if (actor.type.is_real_player && dir2 &&
|
||||||
! old_cell.blocks_leaving(actor, dir2))
|
! old_cell.blocks_leaving(actor, dir2))
|
||||||
{
|
{
|
||||||
let neighbor = this.get_neighboring_cell(old_cell, dir2);
|
let neighbor = this.get_neighboring_cell(old_cell, dir2);
|
||||||
@ -811,6 +832,18 @@ export class Level {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// In the event that the player is sliding (and thus not deliberately moving), remember
|
||||||
|
// their current movement direction.
|
||||||
|
// This is hokey, and doing it here is even hokier, but it seems to match CC2 behavior.
|
||||||
|
if (this.player.movement_cooldown > 0) {
|
||||||
|
if (this.player.type.name === 'player') {
|
||||||
|
this.player1_move = this.player.direction;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
this.player2_move = this.player.direction;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Strip out any destroyed actors from the acting order
|
// Strip out any destroyed actors from the acting order
|
||||||
// FIXME this is O(n), where n is /usually/ small, but i still don't love it
|
// FIXME this is O(n), where n is /usually/ small, but i still don't love it
|
||||||
let p = 0;
|
let p = 0;
|
||||||
@ -999,18 +1032,17 @@ export class Level {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Check for a couple effects that always apply immediately
|
// Check for a couple effects that always apply immediately
|
||||||
// TODO do blocks smash monsters?
|
|
||||||
if (actor === this.player) {
|
if (actor === this.player) {
|
||||||
this.hint_shown = null;
|
this.hint_shown = null;
|
||||||
}
|
}
|
||||||
for (let tile of goal_cell) {
|
for (let tile of goal_cell) {
|
||||||
if (actor.type.is_player && tile.type.is_monster) {
|
if (actor.type.is_real_player && tile.type.is_monster) {
|
||||||
this.fail(tile.type.name);
|
this.fail(tile.type.name);
|
||||||
}
|
}
|
||||||
else if (actor.type.is_monster && tile.type.is_player) {
|
else if (actor.type.is_monster && tile.type.is_real_player) {
|
||||||
this.fail(actor.type.name);
|
this.fail(actor.type.name);
|
||||||
}
|
}
|
||||||
else if (actor.type.is_block && tile.type.is_player) {
|
else if (actor.type.is_block && tile.type.is_real_player) {
|
||||||
this.fail('squished');
|
this.fail('squished');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -142,7 +142,15 @@ export const CC2_TILESET_LAYOUT = {
|
|||||||
hiking_boots: [4, 6],
|
hiking_boots: [4, 6],
|
||||||
lightning_bolt: [5, 6],
|
lightning_bolt: [5, 6],
|
||||||
// weird translucent spiral
|
// weird translucent spiral
|
||||||
// weird translucent red
|
// TODO dopps can push but i don't think they have any other visuals
|
||||||
|
doppelganger1: {
|
||||||
|
base: [7, 6],
|
||||||
|
overlay: 'player',
|
||||||
|
},
|
||||||
|
doppelganger2: {
|
||||||
|
base: [7, 6],
|
||||||
|
overlay: 'player2',
|
||||||
|
},
|
||||||
button_blue: [8, 6],
|
button_blue: [8, 6],
|
||||||
button_green: [9, 6],
|
button_green: [9, 6],
|
||||||
button_red: [10, 6],
|
button_red: [10, 6],
|
||||||
@ -946,10 +954,15 @@ export class Tileset {
|
|||||||
|
|
||||||
if (drawspec.overlay) {
|
if (drawspec.overlay) {
|
||||||
// Goofy overlay thing used for green/purple toggle tiles and
|
// Goofy overlay thing used for green/purple toggle tiles and
|
||||||
// southeast thin walls. Draw the base (a type name), then draw
|
// southeast thin walls. Draw the base (a type name or drawspec), then draw
|
||||||
// the overlay (either a type name or a regular draw spec).
|
// the overlay (either a type name or a regular draw spec).
|
||||||
// TODO chance of infinite recursion here
|
// TODO chance of infinite recursion here
|
||||||
|
if (typeof drawspec.base === 'string') {
|
||||||
this.draw_type(drawspec.base, tile, tic, blit);
|
this.draw_type(drawspec.base, tile, tic, blit);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
this._draw_standard(drawspec.base, tile, tic, blit);
|
||||||
|
}
|
||||||
if (typeof drawspec.overlay === 'string') {
|
if (typeof drawspec.overlay === 'string') {
|
||||||
this.draw_type(drawspec.overlay, tile, tic, blit);
|
this.draw_type(drawspec.overlay, tile, tic, blit);
|
||||||
return;
|
return;
|
||||||
|
|||||||
@ -1716,6 +1716,7 @@ const TILE_TYPES = {
|
|||||||
draw_layer: DRAW_LAYERS.actor,
|
draw_layer: DRAW_LAYERS.actor,
|
||||||
is_actor: true,
|
is_actor: true,
|
||||||
is_player: true,
|
is_player: true,
|
||||||
|
is_real_player: true,
|
||||||
collision_mask: COLLISION.player1,
|
collision_mask: COLLISION.player1,
|
||||||
has_inventory: true,
|
has_inventory: true,
|
||||||
can_reveal_walls: true,
|
can_reveal_walls: true,
|
||||||
@ -1734,6 +1735,7 @@ const TILE_TYPES = {
|
|||||||
draw_layer: DRAW_LAYERS.actor,
|
draw_layer: DRAW_LAYERS.actor,
|
||||||
is_actor: true,
|
is_actor: true,
|
||||||
is_player: true,
|
is_player: true,
|
||||||
|
is_real_player: true,
|
||||||
collision_mask: COLLISION.player2,
|
collision_mask: COLLISION.player2,
|
||||||
has_inventory: true,
|
has_inventory: true,
|
||||||
can_reveal_walls: true,
|
can_reveal_walls: true,
|
||||||
@ -1749,6 +1751,57 @@ const TILE_TYPES = {
|
|||||||
},
|
},
|
||||||
visual_state: player_visual_state,
|
visual_state: player_visual_state,
|
||||||
},
|
},
|
||||||
|
doppelganger1: {
|
||||||
|
draw_layer: DRAW_LAYERS.actor,
|
||||||
|
is_actor: true,
|
||||||
|
is_player: true,
|
||||||
|
is_monster: true,
|
||||||
|
collision_mask: COLLISION.player1,
|
||||||
|
blocks_collision: COLLISION.all_but_player,
|
||||||
|
has_inventory: true,
|
||||||
|
can_reveal_walls: true, // XXX i think?
|
||||||
|
movement_speed: 4,
|
||||||
|
movement_mode: 'copy1',
|
||||||
|
pushes: {
|
||||||
|
dirt_block: true,
|
||||||
|
ice_block: true,
|
||||||
|
directional_block: true,
|
||||||
|
},
|
||||||
|
infinite_items: {
|
||||||
|
key_green: true,
|
||||||
|
},
|
||||||
|
decide_movement(me, level) {
|
||||||
|
if (level.player1_move) {
|
||||||
|
return [level.player1_move];
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
//visual_state: doppelganger_visual_state,
|
||||||
|
},
|
||||||
|
doppelganger2: {
|
||||||
|
draw_layer: DRAW_LAYERS.actor,
|
||||||
|
is_actor: true,
|
||||||
|
is_player: true,
|
||||||
|
is_monster: true,
|
||||||
|
collision_mask: COLLISION.player2,
|
||||||
|
blocks_collision: COLLISION.all_but_player,
|
||||||
|
has_inventory: true,
|
||||||
|
can_reveal_walls: true, // XXX i think?
|
||||||
|
movement_speed: 4,
|
||||||
|
movement_mode: 'copy2',
|
||||||
|
ignores: new Set(['ice', 'ice_nw', 'ice_ne', 'ice_sw', 'ice_se']),
|
||||||
|
pushes: {
|
||||||
|
dirt_block: true,
|
||||||
|
ice_block: true,
|
||||||
|
directional_block: true,
|
||||||
|
},
|
||||||
|
infinite_items: {
|
||||||
|
key_yellow: true,
|
||||||
|
},
|
||||||
|
//visual_state: doppelganger_visual_state,
|
||||||
|
},
|
||||||
chip: {
|
chip: {
|
||||||
draw_layer: DRAW_LAYERS.item,
|
draw_layer: DRAW_LAYERS.item,
|
||||||
is_chip: true,
|
is_chip: true,
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user