Move sliding effects to decision time
This commit is contained in:
parent
bf743caee5
commit
1aa406fc7b
95
js/game.js
95
js/game.js
@ -815,20 +815,25 @@ export class Level extends LevelInterface {
|
|||||||
continue;
|
continue;
|
||||||
|
|
||||||
// Actor is allowed to move, so do so
|
// Actor is allowed to move, so do so
|
||||||
let old_cell = actor.cell;
|
|
||||||
let success = this.attempt_step(actor, actor.decision);
|
let success = this.attempt_step(actor, actor.decision);
|
||||||
|
|
||||||
if (! success && actor.slide_mode === 'ice') {
|
// FIXME not convinced that ice bonking should actually go here. in cc2 it appears to
|
||||||
this._handle_slide_bonk(actor);
|
// happen every frame, fwiw, but i'm not sure if that includes frames with forced moves
|
||||||
success = this.attempt_step(actor, actor.decision);
|
// (though i guess that's impossible)
|
||||||
}
|
if (! success) {
|
||||||
|
let terrain = actor.cell.get_terrain();
|
||||||
|
if (terrain.type.slide_mode === 'ice' && (! actor.ignores(terrain.type.name) ||
|
||||||
// TODO weird cc2 quirk/bug: ghosts bonk on ice even though they don't slide on it
|
// TODO weird cc2 quirk/bug: ghosts bonk on ice even though they don't slide on it
|
||||||
// FIXME and if they have cleats, they get stuck instead (?!)
|
// FIXME and if they have cleats, they get stuck instead (?!)
|
||||||
else if (actor.type.name === 'ghost' && actor.cell.get_terrain().type.slide_mode === 'ice')
|
(actor.type.name === 'ghost' && actor.cell.get_terrain().type.slide_mode === 'ice')))
|
||||||
{
|
{
|
||||||
actor.decision = DIRECTIONS[actor.decision].opposite;
|
// Bonk on ice: turn the actor around, consult the tile in case it's an ice
|
||||||
|
// corner, and try again
|
||||||
|
actor.direction = DIRECTIONS[actor.decision].opposite;
|
||||||
|
actor.decision = terrain.type.get_slide_direction(terrain, this, actor);
|
||||||
success = this.attempt_step(actor, actor.decision);
|
success = this.attempt_step(actor, actor.decision);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Track whether the player is blocked, for visual effect
|
// Track whether the player is blocked, for visual effect
|
||||||
if (actor === this.player && actor.decision && ! success) {
|
if (actor === this.player && actor.decision && ! success) {
|
||||||
@ -949,7 +954,6 @@ export class Level extends LevelInterface {
|
|||||||
|
|
||||||
// TODO player in a cloner can't move (but player in a trap can still turn)
|
// TODO player in a cloner can't move (but player in a trap can still turn)
|
||||||
|
|
||||||
let [dir1, dir2] = this._extract_player_directions(input);
|
|
||||||
let try_direction = (direction, push_mode) => {
|
let try_direction = (direction, push_mode) => {
|
||||||
direction = actor.cell.redirect_exit(actor, direction);
|
direction = actor.cell.redirect_exit(actor, direction);
|
||||||
// FIXME if the player steps into a monster cell here, they die instantly! but only
|
// FIXME if the player steps into a monster cell here, they die instantly! but only
|
||||||
@ -969,7 +973,13 @@ export class Level extends LevelInterface {
|
|||||||
// force floor and attempt to override but /fail/, it's not held against us -- but if we
|
// force floor and attempt to override but /fail/, it's not held against us -- but if we
|
||||||
// succeed, even if overriding in the same direction we're already moving, that does count
|
// succeed, even if overriding in the same direction we're already moving, that does count
|
||||||
// as an override.
|
// as an override.
|
||||||
|
let terrain = actor.cell.get_terrain();
|
||||||
|
let forced_decision;
|
||||||
|
if (actor.slide_mode) {
|
||||||
|
forced_decision = terrain.type.get_slide_direction(terrain, this, actor);
|
||||||
|
}
|
||||||
let may_move = (! actor.slide_mode || (actor.slide_mode === 'force' && actor.last_move_was_force));
|
let may_move = (! actor.slide_mode || (actor.slide_mode === 'force' && actor.last_move_was_force));
|
||||||
|
let [dir1, dir2] = this._extract_player_directions(input);
|
||||||
|
|
||||||
// Check for special player actions, which can only happen at decision time. Dropping can
|
// Check for special player actions, which can only happen at decision time. Dropping can
|
||||||
// only be done when the player is allowed to make a move (i.e. override), but the other two
|
// only be done when the player is allowed to make a move (i.e. override), but the other two
|
||||||
@ -994,19 +1004,12 @@ export class Level extends LevelInterface {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (actor.slide_mode && ! (may_move && dir1)) {
|
if (actor.slide_mode && ! (may_move && dir1)) {
|
||||||
// This is a forced move, in which case we don't even check it
|
// This is a forced move and we're not overriding it, so we're done
|
||||||
actor.decision = actor.direction;
|
actor.decision = forced_decision;
|
||||||
|
|
||||||
if (actor.slide_mode === 'force') {
|
if (actor.slide_mode === 'force') {
|
||||||
this._set_tile_prop(actor, 'last_move_was_force', true);
|
this._set_tile_prop(actor, 'last_move_was_force', true);
|
||||||
}
|
}
|
||||||
else if (actor.slide_mode === 'ice') {
|
|
||||||
// A sliding player that bonks into a wall still needs to turn around, but in this
|
|
||||||
// case they do NOT start pushing blocks early
|
|
||||||
if (! try_direction(actor.direction, 'bump')) {
|
|
||||||
this._handle_slide_bonk(actor);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
else if (dir1 === null) {
|
else if (dir1 === null) {
|
||||||
// Not attempting to move, so do nothing
|
// Not attempting to move, so do nothing
|
||||||
@ -1023,17 +1026,20 @@ export class Level extends LevelInterface {
|
|||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
// We have two directions. If one of them is our current facing, we prefer that
|
// We have two directions. If one of them is our current facing, we prefer that
|
||||||
// one, UNLESS it's blocked AND the other isn't
|
// one, UNLESS it's blocked AND the other isn't.
|
||||||
if (dir1 === actor.direction || dir2 === actor.direction) {
|
// Note that if this is an override, then the forced direction is still used to
|
||||||
let other_direction = dir1 === actor.direction ? dir2 : dir1;
|
// interpret our input!
|
||||||
let curr_open = try_direction(actor.direction, 'push');
|
let facing = forced_decision ?? actor.direction;
|
||||||
|
if (dir1 === facing || dir2 === facing) {
|
||||||
|
let other_direction = dir1 === facing ? dir2 : dir1;
|
||||||
|
let curr_open = try_direction(facing, 'push');
|
||||||
let other_open = try_direction(other_direction, 'push');
|
let other_open = try_direction(other_direction, 'push');
|
||||||
if (! curr_open && other_open) {
|
if (! curr_open && other_open) {
|
||||||
actor.decision = other_direction;
|
actor.decision = other_direction;
|
||||||
open = true;
|
open = true;
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
actor.decision = actor.direction;
|
actor.decision = facing;
|
||||||
open = curr_open;
|
open = curr_open;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1065,11 +1071,9 @@ export class Level extends LevelInterface {
|
|||||||
|
|
||||||
// If we're overriding a force floor but the direction we're moving in is blocked, the
|
// If we're overriding a force floor but the direction we're moving in is blocked, the
|
||||||
// force floor takes priority (and we've already bumped the wall(s))
|
// force floor takes priority (and we've already bumped the wall(s))
|
||||||
if (actor.slide_mode === 'force' && ! open) {
|
if (actor.slide_mode && ! open) {
|
||||||
actor.decision = actor.direction;
|
|
||||||
this._set_tile_prop(actor, 'last_move_was_force', true);
|
this._set_tile_prop(actor, 'last_move_was_force', true);
|
||||||
// Be sure to invoke this hack if we're standing on an RFF
|
actor.decision = forced_decision;
|
||||||
this._handle_slide_bonk(actor);
|
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
// Otherwise this is 100% a conscious move so we lose our override power next tic
|
// Otherwise this is 100% a conscious move so we lose our override power next tic
|
||||||
@ -1100,19 +1104,24 @@ export class Level extends LevelInterface {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let direction_preference;
|
let direction_preference;
|
||||||
if (actor.slide_mode) {
|
let terrain = actor.cell.get_terrain();
|
||||||
|
if (actor.slide_mode ||
|
||||||
|
// TODO weird cc2 quirk/bug: ghosts bonk on ice even though they don't slide on it
|
||||||
|
// FIXME and if they have cleats, they get stuck instead (?!)
|
||||||
|
(actor.type.name === 'ghost' && terrain.type.slide_mode === 'ice'))
|
||||||
|
{
|
||||||
// Actors can't make voluntary moves while sliding; they just, ah, slide.
|
// Actors can't make voluntary moves while sliding; they just, ah, slide.
|
||||||
actor.decision = actor.direction;
|
actor.decision = terrain.type.get_slide_direction(terrain, this, actor);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
else if (actor.cell.some(tile => tile.type.traps && tile.type.traps(tile, actor))) {
|
if (actor.cell.some(tile => tile.type.traps && tile.type.traps(tile, actor))) {
|
||||||
// An actor in a cloner or a closed trap can't turn
|
// An actor in a cloner or a closed trap can't turn
|
||||||
// TODO because of this, if a tank is trapped when a blue button is pressed, then
|
// TODO because of this, if a tank is trapped when a blue button is pressed, then
|
||||||
// when released, it will make one move out of the trap and /then/ turn around and
|
// when released, it will make one move out of the trap and /then/ turn around and
|
||||||
// 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
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
else if (actor.type.decide_movement) {
|
if (actor.type.decide_movement) {
|
||||||
direction_preference = actor.type.decide_movement(actor, this);
|
direction_preference = actor.type.decide_movement(actor, this);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1148,32 +1157,6 @@ export class Level extends LevelInterface {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// FIXME can probably clean this up a decent bit now
|
|
||||||
_handle_slide_bonk(actor) {
|
|
||||||
if (actor.slide_mode === 'ice') {
|
|
||||||
// Actors on ice turn around when they hit something
|
|
||||||
actor.decision = DIRECTIONS[actor.direction].opposite;
|
|
||||||
this.set_actor_direction(actor, actor.decision);
|
|
||||||
}
|
|
||||||
if (actor.slide_mode !== null) {
|
|
||||||
// Somewhat clumsy hack: if an actor is sliding and hits something, step on the
|
|
||||||
// relevant tile again. This fixes two problems: if it was on an ice corner then it
|
|
||||||
// needs to turn a second time even though it didn't move; and if it was a player
|
|
||||||
// overriding a force floor into a wall, then their direction needs to be set back
|
|
||||||
// to the force floor direction.
|
|
||||||
// (For random force floors, this does still match CC2 behavior: after an override,
|
|
||||||
// CC2 will try to force you in the /next/ RFF direction.)
|
|
||||||
// FIXME now overriding into a wall doesn't show you facing that way at all! lynx
|
|
||||||
// only changes your direction at decision time by examining the floor tile...
|
|
||||||
for (let tile of actor.cell) {
|
|
||||||
if (tile.type.slide_mode === actor.slide_mode && tile.type.on_arrive) {
|
|
||||||
tile.type.on_arrive(tile, this, actor);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
actor.decision = actor.direction;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
check_movement(actor, orig_cell, direction, push_mode) {
|
check_movement(actor, orig_cell, direction, push_mode) {
|
||||||
let dest_cell = this.get_neighboring_cell(orig_cell, direction);
|
let dest_cell = this.get_neighboring_cell(orig_cell, direction);
|
||||||
let success = (dest_cell &&
|
let success = (dest_cell &&
|
||||||
|
|||||||
@ -7,15 +7,14 @@ function activate_me(me, level) {
|
|||||||
|
|
||||||
function on_ready_force_floor(me, level) {
|
function on_ready_force_floor(me, level) {
|
||||||
// At the start of the level, if there's an actor on a force floor:
|
// At the start of the level, if there's an actor on a force floor:
|
||||||
// - use on_arrive to set the actor's direction
|
|
||||||
// - set the slide_mode (normally done by the main game loop)
|
// - set the slide_mode (normally done by the main game loop)
|
||||||
// - item bestowal: if they're being pushed into a wall and standing on an item, pick up the
|
// - item bestowal: if they're being pushed into a wall and standing on an item, pick up the
|
||||||
// item, even if they couldn't normally pick items up
|
// item, even if they couldn't normally pick items up
|
||||||
|
// FIXME get rid of this
|
||||||
let actor = me.cell.get_actor();
|
let actor = me.cell.get_actor();
|
||||||
if (! actor)
|
if (! actor)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
me.type.on_arrive(me, level, actor);
|
|
||||||
if (me.type.slide_mode) {
|
if (me.type.slide_mode) {
|
||||||
actor.slide_mode = me.type.slide_mode;
|
actor.slide_mode = me.type.slide_mode;
|
||||||
}
|
}
|
||||||
@ -657,19 +656,21 @@ const TILE_TYPES = {
|
|||||||
ice: {
|
ice: {
|
||||||
draw_layer: DRAW_LAYERS.terrain,
|
draw_layer: DRAW_LAYERS.terrain,
|
||||||
slide_mode: 'ice',
|
slide_mode: 'ice',
|
||||||
|
get_slide_direction(me, level, other) {
|
||||||
|
return other.direction;
|
||||||
|
},
|
||||||
},
|
},
|
||||||
ice_sw: {
|
ice_sw: {
|
||||||
draw_layer: DRAW_LAYERS.terrain,
|
draw_layer: DRAW_LAYERS.terrain,
|
||||||
thin_walls: new Set(['south', 'west']),
|
thin_walls: new Set(['south', 'west']),
|
||||||
slide_mode: 'ice',
|
slide_mode: 'ice',
|
||||||
blocks_leaving: blocks_leaving_thin_walls,
|
blocks_leaving: blocks_leaving_thin_walls,
|
||||||
on_arrive(me, level, other) {
|
get_slide_direction(me, level, other) {
|
||||||
if (other.direction === 'south') {
|
if (other.direction === 'south')
|
||||||
level.set_actor_direction(other, 'east');
|
return 'east';
|
||||||
}
|
if (other.direction === 'west')
|
||||||
else {
|
return 'north';
|
||||||
level.set_actor_direction(other, 'north');
|
return other.direction;
|
||||||
}
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
ice_nw: {
|
ice_nw: {
|
||||||
@ -677,13 +678,12 @@ const TILE_TYPES = {
|
|||||||
thin_walls: new Set(['north', 'west']),
|
thin_walls: new Set(['north', 'west']),
|
||||||
slide_mode: 'ice',
|
slide_mode: 'ice',
|
||||||
blocks_leaving: blocks_leaving_thin_walls,
|
blocks_leaving: blocks_leaving_thin_walls,
|
||||||
on_arrive(me, level, other) {
|
get_slide_direction(me, level, other) {
|
||||||
if (other.direction === 'north') {
|
if (other.direction === 'north')
|
||||||
level.set_actor_direction(other, 'east');
|
return 'east';
|
||||||
}
|
if (other.direction === 'west')
|
||||||
else {
|
return 'south';
|
||||||
level.set_actor_direction(other, 'south');
|
return other.direction;
|
||||||
}
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
ice_ne: {
|
ice_ne: {
|
||||||
@ -691,13 +691,12 @@ const TILE_TYPES = {
|
|||||||
thin_walls: new Set(['north', 'east']),
|
thin_walls: new Set(['north', 'east']),
|
||||||
slide_mode: 'ice',
|
slide_mode: 'ice',
|
||||||
blocks_leaving: blocks_leaving_thin_walls,
|
blocks_leaving: blocks_leaving_thin_walls,
|
||||||
on_arrive(me, level, other) {
|
get_slide_direction(me, level, other) {
|
||||||
if (other.direction === 'north') {
|
if (other.direction === 'north')
|
||||||
level.set_actor_direction(other, 'west');
|
return 'west';
|
||||||
}
|
if (other.direction === 'east')
|
||||||
else {
|
return 'south';
|
||||||
level.set_actor_direction(other, 'south');
|
return other.direction;
|
||||||
}
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
ice_se: {
|
ice_se: {
|
||||||
@ -705,21 +704,20 @@ const TILE_TYPES = {
|
|||||||
thin_walls: new Set(['south', 'east']),
|
thin_walls: new Set(['south', 'east']),
|
||||||
slide_mode: 'ice',
|
slide_mode: 'ice',
|
||||||
blocks_leaving: blocks_leaving_thin_walls,
|
blocks_leaving: blocks_leaving_thin_walls,
|
||||||
on_arrive(me, level, other) {
|
get_slide_direction(me, level, other) {
|
||||||
if (other.direction === 'south') {
|
if (other.direction === 'south')
|
||||||
level.set_actor_direction(other, 'west');
|
return 'west';
|
||||||
}
|
if (other.direction === 'east')
|
||||||
else {
|
return 'north';
|
||||||
level.set_actor_direction(other, 'north');
|
return other.direction;
|
||||||
}
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
force_floor_n: {
|
force_floor_n: {
|
||||||
draw_layer: DRAW_LAYERS.terrain,
|
draw_layer: DRAW_LAYERS.terrain,
|
||||||
slide_mode: 'force',
|
slide_mode: 'force',
|
||||||
on_ready: on_ready_force_floor,
|
on_ready: on_ready_force_floor,
|
||||||
on_arrive(me, level, other) {
|
get_slide_direction(me, level, other) {
|
||||||
level.set_actor_direction(other, 'north');
|
return 'north';
|
||||||
},
|
},
|
||||||
activate(me, level) {
|
activate(me, level) {
|
||||||
level.transmute_tile(me, 'force_floor_s');
|
level.transmute_tile(me, 'force_floor_s');
|
||||||
@ -731,8 +729,8 @@ const TILE_TYPES = {
|
|||||||
draw_layer: DRAW_LAYERS.terrain,
|
draw_layer: DRAW_LAYERS.terrain,
|
||||||
slide_mode: 'force',
|
slide_mode: 'force',
|
||||||
on_ready: on_ready_force_floor,
|
on_ready: on_ready_force_floor,
|
||||||
on_arrive(me, level, other) {
|
get_slide_direction(me, level, other) {
|
||||||
level.set_actor_direction(other, 'east');
|
return 'east';
|
||||||
},
|
},
|
||||||
activate(me, level) {
|
activate(me, level) {
|
||||||
level.transmute_tile(me, 'force_floor_w');
|
level.transmute_tile(me, 'force_floor_w');
|
||||||
@ -744,8 +742,8 @@ const TILE_TYPES = {
|
|||||||
draw_layer: DRAW_LAYERS.terrain,
|
draw_layer: DRAW_LAYERS.terrain,
|
||||||
slide_mode: 'force',
|
slide_mode: 'force',
|
||||||
on_ready: on_ready_force_floor,
|
on_ready: on_ready_force_floor,
|
||||||
on_arrive(me, level, other) {
|
get_slide_direction(me, level, other) {
|
||||||
level.set_actor_direction(other, 'south');
|
return 'south';
|
||||||
},
|
},
|
||||||
activate(me, level) {
|
activate(me, level) {
|
||||||
level.transmute_tile(me, 'force_floor_n');
|
level.transmute_tile(me, 'force_floor_n');
|
||||||
@ -757,8 +755,8 @@ const TILE_TYPES = {
|
|||||||
draw_layer: DRAW_LAYERS.terrain,
|
draw_layer: DRAW_LAYERS.terrain,
|
||||||
slide_mode: 'force',
|
slide_mode: 'force',
|
||||||
on_ready: on_ready_force_floor,
|
on_ready: on_ready_force_floor,
|
||||||
on_arrive(me, level, other) {
|
get_slide_direction(me, level, other) {
|
||||||
level.set_actor_direction(other, 'west');
|
return 'west';
|
||||||
},
|
},
|
||||||
activate(me, level) {
|
activate(me, level) {
|
||||||
level.transmute_tile(me, 'force_floor_e');
|
level.transmute_tile(me, 'force_floor_e');
|
||||||
@ -771,9 +769,8 @@ const TILE_TYPES = {
|
|||||||
slide_mode: 'force',
|
slide_mode: 'force',
|
||||||
on_ready: on_ready_force_floor,
|
on_ready: on_ready_force_floor,
|
||||||
// TODO ms: this is random, and an acting wall to monsters (!)
|
// TODO ms: this is random, and an acting wall to monsters (!)
|
||||||
// TODO lynx/cc2 check this at decision time, which may affect ordering
|
get_slide_direction(me, level, other) {
|
||||||
on_arrive(me, level, other) {
|
return level.get_force_floor_direction();
|
||||||
level.set_actor_direction(other, level.get_force_floor_direction());
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
slime: {
|
slime: {
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user