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',
|
name: 'teeth_timid',
|
||||||
has_next: true,
|
has_next: true,
|
||||||
extra_args: [arg_direction],
|
extra_args: [arg_direction],
|
||||||
error: "Timid chomper is not yet implemented, sorry!",
|
|
||||||
},
|
},
|
||||||
0x58: {
|
0x58: {
|
||||||
// TODO??? unused in main levels -- name: '',
|
// 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;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
has(name) {
|
||||||
|
return this.some(tile => tile.name === name);
|
||||||
|
}
|
||||||
|
|
||||||
blocks_leaving(actor, direction) {
|
blocks_leaving(actor, direction) {
|
||||||
for (let tile of this) {
|
for (let tile of this) {
|
||||||
if (tile === actor)
|
if (tile === actor)
|
||||||
@ -594,21 +598,20 @@ export class Level {
|
|||||||
if (actor.movement_cooldown > 0)
|
if (actor.movement_cooldown > 0)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
// Teeth can only move the first 4 of every 8 tics, though "first"
|
// Teeth can only move the first 4 of every 8 tics, and mimics only the first 4 of every
|
||||||
// can be adjusted
|
// 16, though "first" can be adjusted
|
||||||
if (actor.slide_mode === null &&
|
if (actor.slide_mode === null && actor.type.movement_parity &&
|
||||||
actor.type.uses_teeth_hesitation &&
|
(this.tic_counter + this.step_parity) % (actor.type.movement_parity * 4) >= 4)
|
||||||
(this.tic_counter + this.step_parity) % 8 >= 4)
|
|
||||||
{
|
{
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
let direction_preference;
|
|
||||||
if (this.compat.sliding_tanks_ignore_button &&
|
if (this.compat.sliding_tanks_ignore_button &&
|
||||||
actor.slide_mode && actor.pending_reverse)
|
actor.slide_mode && actor.pending_reverse)
|
||||||
{
|
{
|
||||||
this._set_tile_prop(actor, 'pending_reverse', false);
|
this._set_tile_prop(actor, 'pending_reverse', false);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (actor.pending_push) {
|
if (actor.pending_push) {
|
||||||
// Blocks that were pushed while sliding will move in the push direction as soon as
|
// Blocks that were pushed while sliding will move in the push direction as soon as
|
||||||
// they stop sliding, regardless of what they landed on
|
// they stop sliding, regardless of what they landed on
|
||||||
@ -616,6 +619,8 @@ export class Level {
|
|||||||
this._set_tile_prop(actor, 'pending_push', null);
|
this._set_tile_prop(actor, 'pending_push', null);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let direction_preference;
|
||||||
if (actor.slide_mode === 'ice') {
|
if (actor.slide_mode === 'ice') {
|
||||||
// Actors can't make voluntary moves on ice; they just slide
|
// Actors can't make voluntary moves on ice; they just slide
|
||||||
actor.decision = actor.direction;
|
actor.decision = actor.direction;
|
||||||
@ -637,7 +642,14 @@ export class Level {
|
|||||||
continue;
|
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) {
|
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];
|
direction_preference = [p1_actions.primary];
|
||||||
if (p1_actions.secondary) {
|
if (p1_actions.secondary) {
|
||||||
direction_preference.push(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
|
// 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) {
|
else if (actor.type.decide_movement) {
|
||||||
direction_preference = actor.type.decide_movement(actor, this);
|
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
|
// Check which of those directions we *can*, probably, move in
|
||||||
// TODO i think player on force floor will still have some issues here
|
// TODO i think player on force floor will still have some issues here
|
||||||
if (direction_preference) {
|
if (direction_preference) {
|
||||||
let fallback_direction;
|
|
||||||
for (let [i, direction] of direction_preference.entries()) {
|
for (let [i, direction] of direction_preference.entries()) {
|
||||||
if (direction === 'WALKER') {
|
if (typeof direction === 'function') {
|
||||||
// Walkers roll a random direction ONLY if their first attempt was blocked
|
// Lazy direction calculation (used for walkers)
|
||||||
direction = actor.direction;
|
direction = direction();
|
||||||
let num_turns = this.prng() % 4;
|
|
||||||
for (let i = 0; i < num_turns; i++) {
|
|
||||||
direction = DIRECTIONS[direction].right;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
fallback_direction = direction;
|
|
||||||
|
|
||||||
direction = actor.cell.redirect_exit(actor, 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
|
// 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
|
// In the event that the player is sliding (and thus not deliberately moving) or has
|
||||||
// their current movement direction.
|
// 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.
|
// 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.movement_cooldown > 0) {
|
||||||
if (this.player.type.name === 'player') {
|
this.remember_player_move(this.player.direction);
|
||||||
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
|
||||||
@ -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) {
|
cycle_inventory(actor) {
|
||||||
if (this.stored_level.use_cc1_boots)
|
if (this.stored_level.use_cc1_boots)
|
||||||
return;
|
return;
|
||||||
|
|||||||
@ -852,8 +852,6 @@ const EDITOR_PALETTE = [{
|
|||||||
}, {
|
}, {
|
||||||
title: "Creatures",
|
title: "Creatures",
|
||||||
tiles: [
|
tiles: [
|
||||||
'doppelganger1',
|
|
||||||
'doppelganger2',
|
|
||||||
'tank_blue',
|
'tank_blue',
|
||||||
'tank_yellow',
|
'tank_yellow',
|
||||||
'ball',
|
'ball',
|
||||||
@ -862,8 +860,15 @@ const EDITOR_PALETTE = [{
|
|||||||
'glider',
|
'glider',
|
||||||
'bug',
|
'bug',
|
||||||
'paramecium',
|
'paramecium',
|
||||||
'blob',
|
|
||||||
|
'doppelganger1',
|
||||||
|
'doppelganger2',
|
||||||
'teeth',
|
'teeth',
|
||||||
|
'teeth_timid',
|
||||||
|
'floor_mimic',
|
||||||
|
'ghost',
|
||||||
|
'rover',
|
||||||
|
'blob',
|
||||||
],
|
],
|
||||||
}, {
|
}, {
|
||||||
title: "Mechanisms",
|
title: "Mechanisms",
|
||||||
@ -1146,6 +1151,7 @@ export class Editor extends PrimaryView {
|
|||||||
|
|
||||||
_make_empty_level(number, size_x, size_y) {
|
_make_empty_level(number, size_x, size_y) {
|
||||||
let stored_level = new format_base.StoredLevel(number);
|
let stored_level = new format_base.StoredLevel(number);
|
||||||
|
stored_level.title = "untitled level";
|
||||||
stored_level.size_x = size_x;
|
stored_level.size_x = size_x;
|
||||||
stored_level.size_y = size_y;
|
stored_level.size_y = size_y;
|
||||||
stored_level.viewport_size = 10;
|
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_level = this._make_empty_level(1, 32, 32);
|
||||||
|
|
||||||
let stored_pack = new format_base.StoredPack(null);
|
let stored_pack = new format_base.StoredPack(null);
|
||||||
|
stored_pack.title = "scratch pack";
|
||||||
stored_pack.level_metadata.push({
|
stored_pack.level_metadata.push({
|
||||||
stored_level: stored_level,
|
stored_level: stored_level,
|
||||||
});
|
});
|
||||||
|
|||||||
@ -284,12 +284,11 @@ export const CC2_TILESET_LAYOUT = {
|
|||||||
|
|
||||||
// TODO only animates while moving
|
// TODO only animates while moving
|
||||||
teeth: {
|
teeth: {
|
||||||
// NOTE: CC2 inexplicably dropped north teeth and just uses the south
|
// NOTE: CC2 inexplicably dropped north teeth and just uses the south sprites instead
|
||||||
// sprites instead
|
north: [[1, 11], [0, 11], [1, 11], [2, 11]],
|
||||||
north: [[0, 11], [1, 11], [2, 11]],
|
east: [[4, 11], [3, 11], [4, 11], [5, 11]],
|
||||||
east: [[3, 11], [4, 11], [5, 11]],
|
south: [[1, 11], [0, 11], [1, 11], [2, 11]],
|
||||||
south: [[0, 11], [1, 11], [2, 11]],
|
west: [[7, 11], [6, 11], [7, 11], [8, 11]],
|
||||||
west: [[6, 11], [7, 11], [8, 11]],
|
|
||||||
},
|
},
|
||||||
swivel_sw: [9, 11],
|
swivel_sw: [9, 11],
|
||||||
swivel_nw: [10, 11],
|
swivel_nw: [10, 11],
|
||||||
@ -329,6 +328,14 @@ export const CC2_TILESET_LAYOUT = {
|
|||||||
// TODO [15, 16] some kinda yellow/black outline
|
// TODO [15, 16] some kinda yellow/black outline
|
||||||
|
|
||||||
// timid teeth
|
// 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
|
bowling_ball: [6, 17], // TODO also +18 when rolling
|
||||||
tank_yellow: {
|
tank_yellow: {
|
||||||
north: [[8, 17], [9, 17]],
|
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, {
|
export const LL_TILESET_LAYOUT = Object.assign({}, CC2_TILESET_LAYOUT, {
|
||||||
// Completed teeth sprites
|
// Completed teeth sprites
|
||||||
teeth: Object.assign({}, CC2_TILESET_LAYOUT.teeth, {
|
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
|
// Extra player sprites
|
||||||
player: Object.assign({}, CC2_TILESET_LAYOUT.player, {
|
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 = {
|
const TILE_TYPES = {
|
||||||
// Floors and walls
|
// Floors and walls
|
||||||
floor: {
|
floor: {
|
||||||
@ -996,7 +1034,8 @@ const TILE_TYPES = {
|
|||||||
_mogrifications: {
|
_mogrifications: {
|
||||||
player: 'player2',
|
player: 'player2',
|
||||||
player2: 'player',
|
player2: 'player',
|
||||||
// TODO mirror players too
|
doppelganger1: 'doppelganger2',
|
||||||
|
doppelganger2: 'doppelganger1',
|
||||||
|
|
||||||
dirt_block: 'ice_block',
|
dirt_block: 'ice_block',
|
||||||
ice_block: 'dirt_block',
|
ice_block: 'dirt_block',
|
||||||
@ -1012,9 +1051,10 @@ const TILE_TYPES = {
|
|||||||
tank_blue: 'tank_yellow',
|
tank_blue: 'tank_yellow',
|
||||||
tank_yellow: 'tank_blue',
|
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
|
// TODO can be wired, in which case only works when powered; other minor concerns, see wiki
|
||||||
on_arrive(me, level, other) {
|
on_arrive(me, level, other) {
|
||||||
let name = other.type.name;
|
let name = other.type.name;
|
||||||
@ -1484,8 +1524,12 @@ const TILE_TYPES = {
|
|||||||
is_monster: true,
|
is_monster: true,
|
||||||
collision_mask: COLLISION.bug,
|
collision_mask: COLLISION.bug,
|
||||||
blocks_collision: COLLISION.all_but_player,
|
blocks_collision: COLLISION.all_but_player,
|
||||||
movement_mode: 'follow-left',
|
|
||||||
movement_speed: 4,
|
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: {
|
paramecium: {
|
||||||
draw_layer: DRAW_LAYERS.actor,
|
draw_layer: DRAW_LAYERS.actor,
|
||||||
@ -1493,8 +1537,12 @@ const TILE_TYPES = {
|
|||||||
is_monster: true,
|
is_monster: true,
|
||||||
collision_mask: COLLISION.monster_generic,
|
collision_mask: COLLISION.monster_generic,
|
||||||
blocks_collision: COLLISION.all_but_player,
|
blocks_collision: COLLISION.all_but_player,
|
||||||
movement_mode: 'follow-right',
|
|
||||||
movement_speed: 4,
|
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: {
|
ball: {
|
||||||
draw_layer: DRAW_LAYERS.actor,
|
draw_layer: DRAW_LAYERS.actor,
|
||||||
@ -1502,8 +1550,12 @@ const TILE_TYPES = {
|
|||||||
is_monster: true,
|
is_monster: true,
|
||||||
collision_mask: COLLISION.monster_generic,
|
collision_mask: COLLISION.monster_generic,
|
||||||
blocks_collision: COLLISION.all_but_player,
|
blocks_collision: COLLISION.all_but_player,
|
||||||
movement_mode: 'bounce',
|
|
||||||
movement_speed: 4,
|
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: {
|
walker: {
|
||||||
draw_layer: DRAW_LAYERS.actor,
|
draw_layer: DRAW_LAYERS.actor,
|
||||||
@ -1511,8 +1563,22 @@ const TILE_TYPES = {
|
|||||||
is_monster: true,
|
is_monster: true,
|
||||||
collision_mask: COLLISION.monster_generic,
|
collision_mask: COLLISION.monster_generic,
|
||||||
blocks_collision: COLLISION.all_but_player,
|
blocks_collision: COLLISION.all_but_player,
|
||||||
movement_mode: 'bounce-random',
|
|
||||||
movement_speed: 4,
|
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: {
|
tank_blue: {
|
||||||
draw_layer: DRAW_LAYERS.actor,
|
draw_layer: DRAW_LAYERS.actor,
|
||||||
@ -1520,8 +1586,21 @@ const TILE_TYPES = {
|
|||||||
is_monster: true,
|
is_monster: true,
|
||||||
collision_mask: COLLISION.monster_generic,
|
collision_mask: COLLISION.monster_generic,
|
||||||
blocks_collision: COLLISION.all_but_player,
|
blocks_collision: COLLISION.all_but_player,
|
||||||
movement_mode: 'forward',
|
|
||||||
movement_speed: 4,
|
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: {
|
tank_yellow: {
|
||||||
draw_layer: DRAW_LAYERS.actor,
|
draw_layer: DRAW_LAYERS.actor,
|
||||||
@ -1542,8 +1621,12 @@ const TILE_TYPES = {
|
|||||||
is_monster: true,
|
is_monster: true,
|
||||||
collision_mask: COLLISION.monster_generic,
|
collision_mask: COLLISION.monster_generic,
|
||||||
blocks_collision: COLLISION.all_but_player,
|
blocks_collision: COLLISION.all_but_player,
|
||||||
movement_mode: 'random',
|
|
||||||
movement_speed: 8,
|
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: {
|
teeth: {
|
||||||
draw_layer: DRAW_LAYERS.actor,
|
draw_layer: DRAW_LAYERS.actor,
|
||||||
@ -1551,9 +1634,37 @@ const TILE_TYPES = {
|
|||||||
is_monster: true,
|
is_monster: true,
|
||||||
collision_mask: COLLISION.monster_generic,
|
collision_mask: COLLISION.monster_generic,
|
||||||
blocks_collision: COLLISION.all_but_player,
|
blocks_collision: COLLISION.all_but_player,
|
||||||
movement_mode: 'pursue',
|
|
||||||
movement_speed: 4,
|
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: {
|
fireball: {
|
||||||
draw_layer: DRAW_LAYERS.actor,
|
draw_layer: DRAW_LAYERS.actor,
|
||||||
@ -1561,9 +1672,14 @@ const TILE_TYPES = {
|
|||||||
is_monster: true,
|
is_monster: true,
|
||||||
collision_mask: COLLISION.fireball,
|
collision_mask: COLLISION.fireball,
|
||||||
blocks_collision: COLLISION.all_but_player,
|
blocks_collision: COLLISION.all_but_player,
|
||||||
movement_mode: 'turn-right',
|
|
||||||
movement_speed: 4,
|
movement_speed: 4,
|
||||||
ignores: new Set(['fire', 'flame_jet_on']),
|
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: {
|
glider: {
|
||||||
draw_layer: DRAW_LAYERS.actor,
|
draw_layer: DRAW_LAYERS.actor,
|
||||||
@ -1571,9 +1687,14 @@ const TILE_TYPES = {
|
|||||||
is_monster: true,
|
is_monster: true,
|
||||||
collision_mask: COLLISION.monster_generic,
|
collision_mask: COLLISION.monster_generic,
|
||||||
blocks_collision: COLLISION.all_but_player,
|
blocks_collision: COLLISION.all_but_player,
|
||||||
movement_mode: 'turn-left',
|
|
||||||
movement_speed: 4,
|
movement_speed: 4,
|
||||||
ignores: new Set(['water']),
|
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: {
|
ghost: {
|
||||||
draw_layer: DRAW_LAYERS.actor,
|
draw_layer: DRAW_LAYERS.actor,
|
||||||
@ -1582,9 +1703,15 @@ const TILE_TYPES = {
|
|||||||
collision_mask: COLLISION.ghost,
|
collision_mask: COLLISION.ghost,
|
||||||
blocks_collision: COLLISION.all_but_player,
|
blocks_collision: COLLISION.all_but_player,
|
||||||
has_inventory: true,
|
has_inventory: true,
|
||||||
movement_mode: 'turn-right',
|
|
||||||
movement_speed: 4,
|
movement_speed: 4,
|
||||||
// TODO ignores /most/ walls. collision is basically completely different. has a regular inventory, except red key. good grief
|
// 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: {
|
floor_mimic: {
|
||||||
draw_layer: DRAW_LAYERS.actor,
|
draw_layer: DRAW_LAYERS.actor,
|
||||||
@ -1592,10 +1719,9 @@ const TILE_TYPES = {
|
|||||||
is_monster: true,
|
is_monster: true,
|
||||||
collision_mask: COLLISION.monster_generic,
|
collision_mask: COLLISION.monster_generic,
|
||||||
blocks_collision: COLLISION.all_but_player,
|
blocks_collision: COLLISION.all_but_player,
|
||||||
// TODO not like teeth; always pursues
|
|
||||||
// TODO takes 3 turns off!
|
|
||||||
movement_mode: 'pursue',
|
|
||||||
movement_speed: 4,
|
movement_speed: 4,
|
||||||
|
movement_parity: 4,
|
||||||
|
decide_movement: pursue_player,
|
||||||
},
|
},
|
||||||
rover: {
|
rover: {
|
||||||
// TODO this guy is a nightmare
|
// TODO this guy is a nightmare
|
||||||
@ -1606,7 +1732,6 @@ const TILE_TYPES = {
|
|||||||
collision_mask: COLLISION.rover,
|
collision_mask: COLLISION.rover,
|
||||||
blocks_collision: COLLISION.all_but_player,
|
blocks_collision: COLLISION.all_but_player,
|
||||||
can_reveal_walls: true,
|
can_reveal_walls: true,
|
||||||
movement_mode: 'random',
|
|
||||||
movement_speed: 4,
|
movement_speed: 4,
|
||||||
},
|
},
|
||||||
|
|
||||||
@ -1813,7 +1938,6 @@ const TILE_TYPES = {
|
|||||||
has_inventory: true,
|
has_inventory: true,
|
||||||
can_reveal_walls: true, // XXX i think?
|
can_reveal_walls: true, // XXX i think?
|
||||||
movement_speed: 4,
|
movement_speed: 4,
|
||||||
movement_mode: 'copy1',
|
|
||||||
pushes: {
|
pushes: {
|
||||||
dirt_block: true,
|
dirt_block: true,
|
||||||
ice_block: true,
|
ice_block: true,
|
||||||
@ -1842,7 +1966,6 @@ const TILE_TYPES = {
|
|||||||
has_inventory: true,
|
has_inventory: true,
|
||||||
can_reveal_walls: true, // XXX i think?
|
can_reveal_walls: true, // XXX i think?
|
||||||
movement_speed: 4,
|
movement_speed: 4,
|
||||||
movement_mode: 'copy2',
|
|
||||||
ignores: new Set(['ice', 'ice_nw', 'ice_ne', 'ice_sw', 'ice_se']),
|
ignores: new Set(['ice', 'ice_nw', 'ice_ne', 'ice_sw', 'ice_se']),
|
||||||
pushes: {
|
pushes: {
|
||||||
dirt_block: true,
|
dirt_block: true,
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user