Add timid teeth; move movement decisions onto tile types; improve doppelganger behavior
This commit is contained in:
parent
d981a0a4be
commit
54381370c0
@ -420,7 +420,6 @@ const TILE_ENCODING = {
|
||||
name: 'teeth_timid',
|
||||
has_next: true,
|
||||
extra_args: [arg_direction],
|
||||
error: "Timid chomper is not yet implemented, sorry!",
|
||||
},
|
||||
0x58: {
|
||||
// TODO??? unused in main levels -- name: '',
|
||||
|
||||
160
js/game.js
160
js/game.js
@ -175,6 +175,10 @@ export class Cell extends Array {
|
||||
return null;
|
||||
}
|
||||
|
||||
has(name) {
|
||||
return this.some(tile => tile.name === name);
|
||||
}
|
||||
|
||||
blocks_leaving(actor, direction) {
|
||||
for (let tile of this) {
|
||||
if (tile === actor)
|
||||
@ -594,21 +598,20 @@ export class Level {
|
||||
if (actor.movement_cooldown > 0)
|
||||
continue;
|
||||
|
||||
// Teeth can only move the first 4 of every 8 tics, though "first"
|
||||
// can be adjusted
|
||||
if (actor.slide_mode === null &&
|
||||
actor.type.uses_teeth_hesitation &&
|
||||
(this.tic_counter + this.step_parity) % 8 >= 4)
|
||||
// Teeth can only move the first 4 of every 8 tics, and mimics only the first 4 of every
|
||||
// 16, though "first" can be adjusted
|
||||
if (actor.slide_mode === null && actor.type.movement_parity &&
|
||||
(this.tic_counter + this.step_parity) % (actor.type.movement_parity * 4) >= 4)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
let direction_preference;
|
||||
if (this.compat.sliding_tanks_ignore_button &&
|
||||
actor.slide_mode && actor.pending_reverse)
|
||||
{
|
||||
this._set_tile_prop(actor, 'pending_reverse', false);
|
||||
}
|
||||
|
||||
if (actor.pending_push) {
|
||||
// Blocks that were pushed while sliding will move in the push direction as soon as
|
||||
// they stop sliding, regardless of what they landed on
|
||||
@ -616,6 +619,8 @@ export class Level {
|
||||
this._set_tile_prop(actor, 'pending_push', null);
|
||||
continue;
|
||||
}
|
||||
|
||||
let direction_preference;
|
||||
if (actor.slide_mode === 'ice') {
|
||||
// Actors can't make voluntary moves on ice; they just slide
|
||||
actor.decision = actor.direction;
|
||||
@ -637,7 +642,14 @@ export class Level {
|
||||
continue;
|
||||
}
|
||||
|
||||
// FIXME this isn't right; if primary is blocked, they move secondary, but they also
|
||||
// ignore railroad redirection until next tic
|
||||
this.remember_player_move(p1_actions.primary);
|
||||
|
||||
if (p1_actions.primary) {
|
||||
// FIXME something is wrong with direction preferences! if you hold both keys
|
||||
// in a corner, no matter which you pressed first, cc2 always tries vert first
|
||||
// and horiz last (so you're pushing horizontally)!
|
||||
direction_preference = [p1_actions.primary];
|
||||
if (p1_actions.secondary) {
|
||||
direction_preference.push(p1_actions.secondary);
|
||||
@ -660,116 +672,18 @@ 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;
|
||||
if (actor.pending_reverse) {
|
||||
direction = DIRECTIONS[actor.direction].opposite;
|
||||
this._set_tile_prop(actor, 'pending_reverse', false);
|
||||
}
|
||||
// Tanks are controlled explicitly so they don't check if they're blocked
|
||||
// TODO tanks in traps turn around, but tanks on cloners do not, and i use the same
|
||||
// prop for both
|
||||
if (! actor.cell.some(tile => tile.type.name === 'cloner')) {
|
||||
actor.decision = direction;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
else if (actor.type.movement_mode === 'follow-left') {
|
||||
// bug behavior: always try turning as left as possible, and
|
||||
// fall back to less-left turns when that fails
|
||||
let d = DIRECTIONS[actor.direction];
|
||||
direction_preference = [d.left, actor.direction, d.right, d.opposite];
|
||||
}
|
||||
else if (actor.type.movement_mode === 'follow-right') {
|
||||
// paramecium behavior: always try turning as right as
|
||||
// possible, and fall back to less-right turns when that fails
|
||||
let d = DIRECTIONS[actor.direction];
|
||||
direction_preference = [d.right, actor.direction, d.left, d.opposite];
|
||||
}
|
||||
else if (actor.type.movement_mode === 'turn-left') {
|
||||
// glider behavior: preserve current direction; if that doesn't
|
||||
// work, turn left, then right, then back the way we came
|
||||
let d = DIRECTIONS[actor.direction];
|
||||
direction_preference = [actor.direction, d.left, d.right, d.opposite];
|
||||
}
|
||||
else if (actor.type.movement_mode === 'turn-right') {
|
||||
// fireball behavior: preserve current direction; if that doesn't
|
||||
// work, turn right, then left, then back the way we came
|
||||
let d = DIRECTIONS[actor.direction];
|
||||
direction_preference = [actor.direction, d.right, d.left, d.opposite];
|
||||
}
|
||||
else if (actor.type.movement_mode === 'bounce') {
|
||||
// bouncy ball behavior: preserve current direction; if that
|
||||
// doesn't work, bounce back the way we came
|
||||
let d = DIRECTIONS[actor.direction];
|
||||
direction_preference = [actor.direction, d.opposite];
|
||||
}
|
||||
else if (actor.type.movement_mode === 'bounce-random') {
|
||||
// walker behavior: preserve current direction; if that doesn't work, pick a random
|
||||
// direction, even the one we failed to move in (but ONLY then)
|
||||
direction_preference = [actor.direction, 'WALKER'];
|
||||
}
|
||||
else if (actor.type.movement_mode === 'pursue') {
|
||||
// teeth behavior: always move towards the player
|
||||
let target_cell = this.player.cell;
|
||||
// CC2 behavior (not Lynx (TODO compat?)): pursue the cell the player is leaving, if
|
||||
// they're still mostly in it
|
||||
if (this.player.previous_cell && this.player.animation_speed &&
|
||||
this.player.animation_progress <= this.player.animation_speed / 2)
|
||||
{
|
||||
target_cell = this.player.previous_cell;
|
||||
}
|
||||
let dx = actor.cell.x - target_cell.x;
|
||||
let dy = actor.cell.y - target_cell.y;
|
||||
let preferred_horizontal, preferred_vertical;
|
||||
if (dx > 0) {
|
||||
preferred_horizontal = 'west';
|
||||
}
|
||||
else if (dx < 0) {
|
||||
preferred_horizontal = 'east';
|
||||
}
|
||||
if (dy > 0) {
|
||||
preferred_vertical = 'north';
|
||||
}
|
||||
else if (dy < 0) {
|
||||
preferred_vertical = 'south';
|
||||
}
|
||||
// Chooses the furthest direction, vertical wins ties
|
||||
if (Math.abs(dx) > Math.abs(dy)) {
|
||||
// Horizontal first
|
||||
direction_preference = [preferred_horizontal, preferred_vertical].filter(x => x);
|
||||
}
|
||||
else {
|
||||
// Vertical first
|
||||
direction_preference = [preferred_vertical, preferred_horizontal].filter(x => x);
|
||||
}
|
||||
}
|
||||
else if (actor.type.movement_mode === 'random') {
|
||||
// blob behavior: move completely at random
|
||||
let modifier = this.get_blob_modifier();
|
||||
direction_preference = [['north', 'east', 'south', 'west'][(this.prng() + modifier) % 4]];
|
||||
}
|
||||
|
||||
// Check which of those directions we *can*, probably, move in
|
||||
// TODO i think player on force floor will still have some issues here
|
||||
if (direction_preference) {
|
||||
let fallback_direction;
|
||||
for (let [i, direction] of direction_preference.entries()) {
|
||||
if (direction === 'WALKER') {
|
||||
// Walkers roll a random direction ONLY if their first attempt was blocked
|
||||
direction = actor.direction;
|
||||
let num_turns = this.prng() % 4;
|
||||
for (let i = 0; i < num_turns; i++) {
|
||||
direction = DIRECTIONS[direction].right;
|
||||
}
|
||||
if (typeof direction === 'function') {
|
||||
// Lazy direction calculation (used for walkers)
|
||||
direction = direction();
|
||||
}
|
||||
fallback_direction = direction;
|
||||
|
||||
direction = actor.cell.redirect_exit(actor, direction);
|
||||
|
||||
@ -793,18 +707,6 @@ export class Level {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Do some cleanup for the player
|
||||
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 = actor.decision;
|
||||
}
|
||||
else {
|
||||
this.player2_move = actor.decision;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Third pass: everyone actually moves
|
||||
@ -866,16 +768,11 @@ export class Level {
|
||||
}
|
||||
}
|
||||
|
||||
// In the event that the player is sliding (and thus not deliberately moving), remember
|
||||
// their current movement direction.
|
||||
// In the event that the player is sliding (and thus not deliberately moving) or has
|
||||
// stopped, remember their current movement direction here, too.
|
||||
// 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;
|
||||
}
|
||||
this.remember_player_move(this.player.direction);
|
||||
}
|
||||
|
||||
// Strip out any destroyed actors from the acting order
|
||||
@ -1208,6 +1105,15 @@ export class Level {
|
||||
}
|
||||
}
|
||||
|
||||
remember_player_move(direction) {
|
||||
if (this.player.type.name === 'player') {
|
||||
this.player1_move = direction;
|
||||
}
|
||||
else {
|
||||
this.player2_move = direction;
|
||||
}
|
||||
}
|
||||
|
||||
cycle_inventory(actor) {
|
||||
if (this.stored_level.use_cc1_boots)
|
||||
return;
|
||||
|
||||
@ -852,8 +852,6 @@ const EDITOR_PALETTE = [{
|
||||
}, {
|
||||
title: "Creatures",
|
||||
tiles: [
|
||||
'doppelganger1',
|
||||
'doppelganger2',
|
||||
'tank_blue',
|
||||
'tank_yellow',
|
||||
'ball',
|
||||
@ -862,8 +860,15 @@ const EDITOR_PALETTE = [{
|
||||
'glider',
|
||||
'bug',
|
||||
'paramecium',
|
||||
'blob',
|
||||
|
||||
'doppelganger1',
|
||||
'doppelganger2',
|
||||
'teeth',
|
||||
'teeth_timid',
|
||||
'floor_mimic',
|
||||
'ghost',
|
||||
'rover',
|
||||
'blob',
|
||||
],
|
||||
}, {
|
||||
title: "Mechanisms",
|
||||
@ -1146,6 +1151,7 @@ export class Editor extends PrimaryView {
|
||||
|
||||
_make_empty_level(number, size_x, size_y) {
|
||||
let stored_level = new format_base.StoredLevel(number);
|
||||
stored_level.title = "untitled level";
|
||||
stored_level.size_x = size_x;
|
||||
stored_level.size_y = size_y;
|
||||
stored_level.viewport_size = 10;
|
||||
@ -1206,6 +1212,7 @@ export class Editor extends PrimaryView {
|
||||
let stored_level = this._make_empty_level(1, 32, 32);
|
||||
|
||||
let stored_pack = new format_base.StoredPack(null);
|
||||
stored_pack.title = "scratch pack";
|
||||
stored_pack.level_metadata.push({
|
||||
stored_level: stored_level,
|
||||
});
|
||||
|
||||
@ -284,12 +284,11 @@ export const CC2_TILESET_LAYOUT = {
|
||||
|
||||
// TODO only animates while moving
|
||||
teeth: {
|
||||
// NOTE: CC2 inexplicably dropped north teeth and just uses the south
|
||||
// sprites instead
|
||||
north: [[0, 11], [1, 11], [2, 11]],
|
||||
east: [[3, 11], [4, 11], [5, 11]],
|
||||
south: [[0, 11], [1, 11], [2, 11]],
|
||||
west: [[6, 11], [7, 11], [8, 11]],
|
||||
// NOTE: CC2 inexplicably dropped north teeth and just uses the south sprites instead
|
||||
north: [[1, 11], [0, 11], [1, 11], [2, 11]],
|
||||
east: [[4, 11], [3, 11], [4, 11], [5, 11]],
|
||||
south: [[1, 11], [0, 11], [1, 11], [2, 11]],
|
||||
west: [[7, 11], [6, 11], [7, 11], [8, 11]],
|
||||
},
|
||||
swivel_sw: [9, 11],
|
||||
swivel_nw: [10, 11],
|
||||
@ -329,6 +328,14 @@ export const CC2_TILESET_LAYOUT = {
|
||||
// TODO [15, 16] some kinda yellow/black outline
|
||||
|
||||
// timid teeth
|
||||
teeth_timid: {
|
||||
// NOTE: CC2 inexplicably dropped north teeth and just uses the south sprites instead
|
||||
// NOTE: it also skimped on timid teeth frames
|
||||
north: [[1, 17], [0, 17]],
|
||||
east: [[3, 17], [2, 17]],
|
||||
south: [[1, 17], [0, 17]],
|
||||
west: [[5, 17], [4, 17]],
|
||||
},
|
||||
bowling_ball: [6, 17], // TODO also +18 when rolling
|
||||
tank_yellow: {
|
||||
north: [[8, 17], [9, 17]],
|
||||
@ -763,8 +770,14 @@ export const TILE_WORLD_TILESET_LAYOUT = {
|
||||
export const LL_TILESET_LAYOUT = Object.assign({}, CC2_TILESET_LAYOUT, {
|
||||
// Completed teeth sprites
|
||||
teeth: Object.assign({}, CC2_TILESET_LAYOUT.teeth, {
|
||||
north: [[0, 32], [1, 32], [2, 32], [1, 32]],
|
||||
north: [[1, 32], [0, 32], [1, 32], [2, 32]],
|
||||
}),
|
||||
teeth_timid: {
|
||||
north: [[7, 32], [6, 32], [7, 32], [8, 32]],
|
||||
east: [[4, 32], [2, 17], [4, 32], [3, 17]],
|
||||
south: [[3, 32], [0, 17], [3, 32], [1, 17]],
|
||||
west: [[5, 32], [4, 17], [5, 32], [5, 17]],
|
||||
},
|
||||
|
||||
// Extra player sprites
|
||||
player: Object.assign({}, CC2_TILESET_LAYOUT.player, {
|
||||
|
||||
163
js/tiletypes.js
163
js/tiletypes.js
@ -83,6 +83,44 @@ function player_visual_state(me) {
|
||||
}
|
||||
}
|
||||
|
||||
// Logic for chasing after the player (or running away); shared by both teeth and mimics
|
||||
function pursue_player(me, level) {
|
||||
let player = level.player;
|
||||
let target_cell = player.cell;
|
||||
// CC2 behavior (not Lynx (TODO compat?)): pursue the cell the player is leaving, if
|
||||
// they're still mostly in it
|
||||
if (player.previous_cell && player.animation_speed &&
|
||||
player.animation_progress <= player.animation_speed / 2)
|
||||
{
|
||||
target_cell = player.previous_cell;
|
||||
}
|
||||
|
||||
let dx = me.cell.x - target_cell.x;
|
||||
let dy = me.cell.y - target_cell.y;
|
||||
let preferred_horizontal, preferred_vertical;
|
||||
if (dx > 0) {
|
||||
preferred_horizontal = 'west';
|
||||
}
|
||||
else if (dx < 0) {
|
||||
preferred_horizontal = 'east';
|
||||
}
|
||||
if (dy > 0) {
|
||||
preferred_vertical = 'north';
|
||||
}
|
||||
else if (dy < 0) {
|
||||
preferred_vertical = 'south';
|
||||
}
|
||||
// Chooses the furthest direction, vertical wins ties
|
||||
if (Math.abs(dx) > Math.abs(dy)) {
|
||||
// Horizontal first
|
||||
return [preferred_horizontal, preferred_vertical].filter(x => x);
|
||||
}
|
||||
else {
|
||||
// Vertical first
|
||||
return [preferred_vertical, preferred_horizontal].filter(x => x);
|
||||
}
|
||||
}
|
||||
|
||||
const TILE_TYPES = {
|
||||
// Floors and walls
|
||||
floor: {
|
||||
@ -996,7 +1034,8 @@ const TILE_TYPES = {
|
||||
_mogrifications: {
|
||||
player: 'player2',
|
||||
player2: 'player',
|
||||
// TODO mirror players too
|
||||
doppelganger1: 'doppelganger2',
|
||||
doppelganger2: 'doppelganger1',
|
||||
|
||||
dirt_block: 'ice_block',
|
||||
ice_block: 'dirt_block',
|
||||
@ -1012,9 +1051,10 @@ const TILE_TYPES = {
|
||||
tank_blue: 'tank_yellow',
|
||||
tank_yellow: 'tank_blue',
|
||||
|
||||
// TODO teeth, timid teeth
|
||||
teeth: 'teeth_timid',
|
||||
teeth_timid: 'teeth',
|
||||
},
|
||||
_blob_mogrifications: ['ball', 'walker', 'fireball', 'glider', 'paramecium', 'bug', 'tank_blue', 'teeth', /* TODO 'timid_teeth' */ ],
|
||||
_blob_mogrifications: ['ball', 'walker', 'fireball', 'glider', 'paramecium', 'bug', 'tank_blue', 'teeth', 'teeth_timid'],
|
||||
// TODO can be wired, in which case only works when powered; other minor concerns, see wiki
|
||||
on_arrive(me, level, other) {
|
||||
let name = other.type.name;
|
||||
@ -1484,8 +1524,12 @@ const TILE_TYPES = {
|
||||
is_monster: true,
|
||||
collision_mask: COLLISION.bug,
|
||||
blocks_collision: COLLISION.all_but_player,
|
||||
movement_mode: 'follow-left',
|
||||
movement_speed: 4,
|
||||
decide_movement(me, level) {
|
||||
// always try turning as left as possible, and fall back to less-left turns
|
||||
let d = DIRECTIONS[me.direction];
|
||||
return [d.left, me.direction, d.right, d.opposite];
|
||||
},
|
||||
},
|
||||
paramecium: {
|
||||
draw_layer: DRAW_LAYERS.actor,
|
||||
@ -1493,8 +1537,12 @@ const TILE_TYPES = {
|
||||
is_monster: true,
|
||||
collision_mask: COLLISION.monster_generic,
|
||||
blocks_collision: COLLISION.all_but_player,
|
||||
movement_mode: 'follow-right',
|
||||
movement_speed: 4,
|
||||
decide_movement(me, level) {
|
||||
// always try turning as right as possible, and fall back to less-right turns
|
||||
let d = DIRECTIONS[me.direction];
|
||||
return [d.right, me.direction, d.left, d.opposite];
|
||||
},
|
||||
},
|
||||
ball: {
|
||||
draw_layer: DRAW_LAYERS.actor,
|
||||
@ -1502,8 +1550,12 @@ const TILE_TYPES = {
|
||||
is_monster: true,
|
||||
collision_mask: COLLISION.monster_generic,
|
||||
blocks_collision: COLLISION.all_but_player,
|
||||
movement_mode: 'bounce',
|
||||
movement_speed: 4,
|
||||
decide_movement(me, level) {
|
||||
// preserve current direction; if that doesn't work, bounce back the way we came
|
||||
let d = DIRECTIONS[me.direction];
|
||||
return [me.direction, d.opposite];
|
||||
},
|
||||
},
|
||||
walker: {
|
||||
draw_layer: DRAW_LAYERS.actor,
|
||||
@ -1511,8 +1563,22 @@ const TILE_TYPES = {
|
||||
is_monster: true,
|
||||
collision_mask: COLLISION.monster_generic,
|
||||
blocks_collision: COLLISION.all_but_player,
|
||||
movement_mode: 'bounce-random',
|
||||
movement_speed: 4,
|
||||
decide_movement(me, level) {
|
||||
// preserve current direction; if that doesn't work, pick a random direction, even the
|
||||
// one we failed to move in (but ONLY then; important for RNG sync)
|
||||
return [
|
||||
me.direction,
|
||||
() => {
|
||||
let direction = me.direction;
|
||||
let num_turns = level.prng() % 4;
|
||||
for (let i = 0; i < num_turns; i++) {
|
||||
direction = DIRECTIONS[direction].right;
|
||||
}
|
||||
return direction;
|
||||
},
|
||||
];
|
||||
},
|
||||
},
|
||||
tank_blue: {
|
||||
draw_layer: DRAW_LAYERS.actor,
|
||||
@ -1520,8 +1586,21 @@ const TILE_TYPES = {
|
||||
is_monster: true,
|
||||
collision_mask: COLLISION.monster_generic,
|
||||
blocks_collision: COLLISION.all_but_player,
|
||||
movement_mode: 'forward',
|
||||
movement_speed: 4,
|
||||
decide_movement(me, level) {
|
||||
// always keep moving forward, but reverse if the flag is set
|
||||
let direction = me.direction;
|
||||
if (me.pending_reverse) {
|
||||
direction = DIRECTIONS[me.direction].opposite;
|
||||
level._set_tile_prop(me, 'pending_reverse', false);
|
||||
}
|
||||
if (me.cell.has('cloner')) {
|
||||
// Tanks on cloners should definitely ignore the flag, but we clear it first
|
||||
// TODO feels clumsy
|
||||
return null;
|
||||
}
|
||||
return [direction];
|
||||
}
|
||||
},
|
||||
tank_yellow: {
|
||||
draw_layer: DRAW_LAYERS.actor,
|
||||
@ -1542,8 +1621,12 @@ const TILE_TYPES = {
|
||||
is_monster: true,
|
||||
collision_mask: COLLISION.monster_generic,
|
||||
blocks_collision: COLLISION.all_but_player,
|
||||
movement_mode: 'random',
|
||||
movement_speed: 8,
|
||||
decide_movement(me, level) {
|
||||
// move completely at random
|
||||
let modifier = level.get_blob_modifier();
|
||||
return [['north', 'east', 'south', 'west'][(level.prng() + modifier) % 4]];
|
||||
},
|
||||
},
|
||||
teeth: {
|
||||
draw_layer: DRAW_LAYERS.actor,
|
||||
@ -1551,9 +1634,37 @@ const TILE_TYPES = {
|
||||
is_monster: true,
|
||||
collision_mask: COLLISION.monster_generic,
|
||||
blocks_collision: COLLISION.all_but_player,
|
||||
movement_mode: 'pursue',
|
||||
movement_speed: 4,
|
||||
uses_teeth_hesitation: true,
|
||||
movement_parity: 2,
|
||||
decide_movement(me, level) {
|
||||
let preference = pursue_player(me, level);
|
||||
if (level.player.type.name === 'player2') {
|
||||
// Run away from Cerise
|
||||
for (let [i, direction] of preference.entries()) {
|
||||
preference[i] = DIRECTIONS[direction].opposite;
|
||||
}
|
||||
}
|
||||
return preference;
|
||||
},
|
||||
},
|
||||
teeth_timid: {
|
||||
draw_layer: DRAW_LAYERS.actor,
|
||||
is_actor: true,
|
||||
is_monster: true,
|
||||
collision_mask: COLLISION.monster_generic,
|
||||
blocks_collision: COLLISION.all_but_player,
|
||||
movement_speed: 4,
|
||||
movement_parity: 2,
|
||||
decide_movement(me, level) {
|
||||
let preference = pursue_player(me, level);
|
||||
if (level.player.type.name === 'player') {
|
||||
// Run away from Lexy
|
||||
for (let [i, direction] of preference.entries()) {
|
||||
preference[i] = DIRECTIONS[direction].opposite;
|
||||
}
|
||||
}
|
||||
return preference;
|
||||
},
|
||||
},
|
||||
fireball: {
|
||||
draw_layer: DRAW_LAYERS.actor,
|
||||
@ -1561,9 +1672,14 @@ const TILE_TYPES = {
|
||||
is_monster: true,
|
||||
collision_mask: COLLISION.fireball,
|
||||
blocks_collision: COLLISION.all_but_player,
|
||||
movement_mode: 'turn-right',
|
||||
movement_speed: 4,
|
||||
ignores: new Set(['fire', 'flame_jet_on']),
|
||||
decide_movement(me, level) {
|
||||
// turn right: preserve current direction; if that doesn't work, turn right, then left,
|
||||
// then back the way we came
|
||||
let d = DIRECTIONS[me.direction];
|
||||
return [me.direction, d.right, d.left, d.opposite];
|
||||
},
|
||||
},
|
||||
glider: {
|
||||
draw_layer: DRAW_LAYERS.actor,
|
||||
@ -1571,9 +1687,14 @@ const TILE_TYPES = {
|
||||
is_monster: true,
|
||||
collision_mask: COLLISION.monster_generic,
|
||||
blocks_collision: COLLISION.all_but_player,
|
||||
movement_mode: 'turn-left',
|
||||
movement_speed: 4,
|
||||
ignores: new Set(['water']),
|
||||
decide_movement(me, level) {
|
||||
// turn left: preserve current direction; if that doesn't work, turn left, then right,
|
||||
// then back the way we came
|
||||
let d = DIRECTIONS[me.direction];
|
||||
return [me.direction, d.left, d.right, d.opposite];
|
||||
},
|
||||
},
|
||||
ghost: {
|
||||
draw_layer: DRAW_LAYERS.actor,
|
||||
@ -1582,9 +1703,15 @@ const TILE_TYPES = {
|
||||
collision_mask: COLLISION.ghost,
|
||||
blocks_collision: COLLISION.all_but_player,
|
||||
has_inventory: true,
|
||||
movement_mode: 'turn-right',
|
||||
movement_speed: 4,
|
||||
// TODO ignores /most/ walls. collision is basically completely different. has a regular inventory, except red key. good grief
|
||||
decide_movement(me, level) {
|
||||
// turn right: preserve current direction; if that doesn't work, turn right, then left,
|
||||
// then back the way we came
|
||||
// (same as fireball)
|
||||
let d = DIRECTIONS[me.direction];
|
||||
return [me.direction, d.right, d.left, d.opposite];
|
||||
},
|
||||
},
|
||||
floor_mimic: {
|
||||
draw_layer: DRAW_LAYERS.actor,
|
||||
@ -1592,10 +1719,9 @@ const TILE_TYPES = {
|
||||
is_monster: true,
|
||||
collision_mask: COLLISION.monster_generic,
|
||||
blocks_collision: COLLISION.all_but_player,
|
||||
// TODO not like teeth; always pursues
|
||||
// TODO takes 3 turns off!
|
||||
movement_mode: 'pursue',
|
||||
movement_speed: 4,
|
||||
movement_parity: 4,
|
||||
decide_movement: pursue_player,
|
||||
},
|
||||
rover: {
|
||||
// TODO this guy is a nightmare
|
||||
@ -1606,7 +1732,6 @@ const TILE_TYPES = {
|
||||
collision_mask: COLLISION.rover,
|
||||
blocks_collision: COLLISION.all_but_player,
|
||||
can_reveal_walls: true,
|
||||
movement_mode: 'random',
|
||||
movement_speed: 4,
|
||||
},
|
||||
|
||||
@ -1813,7 +1938,6 @@ const TILE_TYPES = {
|
||||
has_inventory: true,
|
||||
can_reveal_walls: true, // XXX i think?
|
||||
movement_speed: 4,
|
||||
movement_mode: 'copy1',
|
||||
pushes: {
|
||||
dirt_block: true,
|
||||
ice_block: true,
|
||||
@ -1842,7 +1966,6 @@ const TILE_TYPES = {
|
||||
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,
|
||||
|
||||
Loading…
Reference in New Issue
Block a user