Add a basic implementation of doppelgangers
This commit is contained in:
parent
f0680ce0c4
commit
8d197ce479
@ -496,13 +496,11 @@ const TILE_ENCODING = {
|
||||
name: 'doppelganger1',
|
||||
has_next: true,
|
||||
extra_args: [arg_direction],
|
||||
error: "Doppelganger Lexy is not yet implemented, sorry!",
|
||||
},
|
||||
0x66: {
|
||||
name: 'doppelganger2',
|
||||
has_next: true,
|
||||
extra_args: [arg_direction],
|
||||
error: "Doppelganger Cerise is not yet implemented, sorry!",
|
||||
},
|
||||
0x68: {
|
||||
name: 'bowling_ball',
|
||||
|
||||
56
js/game.js
56
js/game.js
@ -48,10 +48,10 @@ export class Tile {
|
||||
// the name is the same?
|
||||
blocks(other, direction, level) {
|
||||
// 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
|
||||
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;
|
||||
|
||||
if (this.type.blocks_collision & other.type.collision_mask)
|
||||
@ -281,6 +281,7 @@ export class Level {
|
||||
let n = 0;
|
||||
let connectables = [];
|
||||
this.power_sources = [];
|
||||
this.players = [];
|
||||
// FIXME handle traps correctly:
|
||||
// - 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++) {
|
||||
@ -304,10 +305,8 @@ export class Level {
|
||||
this.power_sources.push(tile);
|
||||
}
|
||||
|
||||
if (tile.type.is_player) {
|
||||
// TODO handle multiple players, also chip and melinda both
|
||||
// TODO complain if no player
|
||||
this.player = tile;
|
||||
if (tile.type.is_real_player) {
|
||||
this.players.push(tile);
|
||||
}
|
||||
if (tile.type.is_actor) {
|
||||
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
|
||||
let num_cells = this.width * this.height;
|
||||
@ -485,7 +490,8 @@ export class Level {
|
||||
for (let key of [
|
||||
'_rng1', '_rng2', '_blob_modifier', 'force_floor_direction',
|
||||
'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];
|
||||
}
|
||||
@ -608,6 +614,7 @@ export class Level {
|
||||
// XXX this in particular has some subtleties in lynx (e.g. you
|
||||
// can override forwards??) and DEFINITELY all kinds of stuff
|
||||
// in ms
|
||||
// XXX unclear what impact this has on doppelgangers
|
||||
if (actor === this.player &&
|
||||
p1_primary_direction &&
|
||||
actor.last_move_was_force)
|
||||
@ -624,6 +631,15 @@ export class Level {
|
||||
continue;
|
||||
}
|
||||
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) {
|
||||
actor.decision = p1_primary_direction;
|
||||
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
|
||||
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') {
|
||||
// blue tank behavior: keep moving forward, reverse if the flag is set
|
||||
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
|
||||
let dir2 = actor.secondary_direction;
|
||||
if (actor.type.is_player && dir2 &&
|
||||
if (actor.type.is_real_player && dir2 &&
|
||||
! old_cell.blocks_leaving(actor, 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
|
||||
// FIXME this is O(n), where n is /usually/ small, but i still don't love it
|
||||
let p = 0;
|
||||
@ -999,18 +1032,17 @@ export class Level {
|
||||
}
|
||||
|
||||
// Check for a couple effects that always apply immediately
|
||||
// TODO do blocks smash monsters?
|
||||
if (actor === this.player) {
|
||||
this.hint_shown = null;
|
||||
}
|
||||
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);
|
||||
}
|
||||
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);
|
||||
}
|
||||
else if (actor.type.is_block && tile.type.is_player) {
|
||||
else if (actor.type.is_block && tile.type.is_real_player) {
|
||||
this.fail('squished');
|
||||
}
|
||||
|
||||
|
||||
@ -142,7 +142,15 @@ export const CC2_TILESET_LAYOUT = {
|
||||
hiking_boots: [4, 6],
|
||||
lightning_bolt: [5, 6],
|
||||
// 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_green: [9, 6],
|
||||
button_red: [10, 6],
|
||||
@ -946,10 +954,15 @@ export class Tileset {
|
||||
|
||||
if (drawspec.overlay) {
|
||||
// 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).
|
||||
// TODO chance of infinite recursion here
|
||||
this.draw_type(drawspec.base, tile, tic, blit);
|
||||
if (typeof drawspec.base === 'string') {
|
||||
this.draw_type(drawspec.base, tile, tic, blit);
|
||||
}
|
||||
else {
|
||||
this._draw_standard(drawspec.base, tile, tic, blit);
|
||||
}
|
||||
if (typeof drawspec.overlay === 'string') {
|
||||
this.draw_type(drawspec.overlay, tile, tic, blit);
|
||||
return;
|
||||
|
||||
@ -1716,6 +1716,7 @@ const TILE_TYPES = {
|
||||
draw_layer: DRAW_LAYERS.actor,
|
||||
is_actor: true,
|
||||
is_player: true,
|
||||
is_real_player: true,
|
||||
collision_mask: COLLISION.player1,
|
||||
has_inventory: true,
|
||||
can_reveal_walls: true,
|
||||
@ -1734,6 +1735,7 @@ const TILE_TYPES = {
|
||||
draw_layer: DRAW_LAYERS.actor,
|
||||
is_actor: true,
|
||||
is_player: true,
|
||||
is_real_player: true,
|
||||
collision_mask: COLLISION.player2,
|
||||
has_inventory: true,
|
||||
can_reveal_walls: true,
|
||||
@ -1749,6 +1751,57 @@ const TILE_TYPES = {
|
||||
},
|
||||
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: {
|
||||
draw_layer: DRAW_LAYERS.item,
|
||||
is_chip: true,
|
||||
|
||||
Loading…
Reference in New Issue
Block a user