Add a basic implementation of doppelgangers

This commit is contained in:
Eevee (Evelyn Woods) 2020-12-02 15:03:13 -07:00
parent f0680ce0c4
commit 8d197ce479
4 changed files with 113 additions and 17 deletions

View File

@ -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',

View File

@ -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');
}

View File

@ -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
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;

View File

@ -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,