Make sliding be the tiles' responsibility

This seems to simplify things and also explain the CC2 semantics: force
floors activate while being stood on (which happens, I guess, during
idle), so it applies to objects that start the level on force floors.
This was probably done to make force floor flipping work, too.  On the
other hand, ice still only activates when being stepped on.
This commit is contained in:
Eevee (Evelyn Woods) 2024-04-10 18:02:43 -06:00
parent 7ba261c7d9
commit a1f357f317
2 changed files with 64 additions and 106 deletions

View File

@ -1238,25 +1238,6 @@ export class Level extends LevelInterface {
} }
this.pending_green_toggle = false; this.pending_green_toggle = false;
} }
// On the very first tic, check for any actors standing on force floors, and set their slide
// directions. Done here because they do NOT move yet, even if unblocked!
// TODO this feels oddly artificial. is this supposed to happen during idle, maybe?
// FIXME check compat flag for lynx
if (this.tic_counter === 0 && this.frame_offset === 0) {
for (let i = this.actors.length - 1; i >= 0; i--) {
let actor = this.actors[i];
let terrain = actor.cell.get_terrain();
if (terrain && terrain.type.slide_mode === 'force') {
let forced_move = this.get_forced_move(actor, terrain);
if (forced_move) {
this._set_tile_prop(actor, 'is_pending_slide', true)
this.set_actor_direction(actor, forced_move);
}
}
}
}
} }
_do_cleanup_phase() { _do_cleanup_phase() {
@ -1338,22 +1319,6 @@ export class Level extends LevelInterface {
return [dir1, dir2]; return [dir1, dir2];
} }
get_forced_move(actor, terrain = null) {
if (! terrain) {
terrain = actor.cell.get_terrain();
}
if (! terrain.type.slide_mode)
return null;
if (! terrain.type.get_slide_direction)
return null;
if (! (actor.is_pending_slide || terrain.type.slide_automatically))
return null;
if (actor.ignores(terrain.type.name))
return null;
return terrain.type.get_slide_direction(terrain, this, actor);
}
make_player_decision(actor, input, forced_only = false) { make_player_decision(actor, input, forced_only = false) {
// Only reset the player's is_pushing between movement, so it lasts for the whole push // Only reset the player's is_pushing between movement, so it lasts for the whole push
this._set_tile_prop(actor, 'is_pushing', false); this._set_tile_prop(actor, 'is_pushing', false);
@ -2048,23 +2013,23 @@ export class Level extends LevelInterface {
// Kind of weird putting slide_ignores here, except that all sliding happens on // Kind of weird putting slide_ignores here, except that all sliding happens on
// on_arrive, and tiles that make you slide in on_arrive don't do anything else, so // on_arrive, and tiles that make you slide in on_arrive don't do anything else, so
// for now it works // for now it works
// XXX that is jank as hell what are you talking about
tile.type.on_arrive(tile, this, actor); tile.type.on_arrive(tile, this, actor);
} }
if (tile.type.on_stand && !actor.slide_ignores(tile.type.name)) {
// XXX according to notcc, cc2 also has actors "stand" on tiles immediately upon
// arrival, even though they'll do it anyway on their idle phase
tile.type.on_stand(tile, this, actor);
}
if (tile.type.slide_automatically) { if (tile.type.slide_automatically) {
// This keeps a player on force floor consistently using their sliding pose, even if // This keeps a player on force floor consistently using their sliding pose, even if
// drawn between moves. It also simplifies checks elsewhere, so that's nice // drawn between moves. It also simplifies checks elsewhere, so that's nice
// FIXME if i'm right about how this works then this may not be necessary?
this._set_tile_prop(actor, 'is_pending_slide', true); this._set_tile_prop(actor, 'is_pending_slide', true);
} }
} }
// FIXME ingratiate this with the rest of this stuff i think
// FIXME figure out what the hell that comment means
let forced_move = this.get_forced_move(actor);
if (forced_move) {
this._set_tile_prop(actor, 'is_pending_slide', true)
this.set_actor_direction(actor, forced_move);
}
} }
attempt_teleport(actor) { attempt_teleport(actor) {
@ -3031,4 +2996,11 @@ export class Level extends LevelInterface {
set_actor_direction(actor, direction) { set_actor_direction(actor, direction) {
this._set_tile_prop(actor, 'direction', direction); this._set_tile_prop(actor, 'direction', direction);
} }
schedule_actor_slide(actor, direction = null) {
if (direction) {
this.set_actor_direction(actor, direction);
}
this._set_tile_prop(actor, 'is_pending_slide', true);
}
} }

View File

@ -58,14 +58,17 @@ function _define_force_floor(direction, opposite_type) {
speed_factor: 2, speed_factor: 2,
slide_automatically: true, slide_automatically: true,
allow_player_override: true, allow_player_override: true,
get_slide_direction(me, level, other) { on_stand(me, level, other) {
level.schedule_actor_slide(other, direction);
// FIXME i think it's really that in lynx these push on arrival; try that and see if
// it's better
/*
if (level.compat.force_floors_inert_on_first_tic && level.tic_counter === 0) { if (level.compat.force_floors_inert_on_first_tic && level.tic_counter === 0) {
// Lynx: Force floors don't push on the first tic // Lynx: Force floors don't push on the first tic
return null; return null;
} }
return direction; */
}, },
force_floor_direction: direction,
activate(me, level) { activate(me, level) {
level.transmute_tile(me, opposite_type); level.transmute_tile(me, opposite_type);
}, },
@ -744,9 +747,6 @@ const TILE_TYPES = {
contains_wire: true, contains_wire: true,
wire_propagation_mode: 'all', wire_propagation_mode: 'all',
slide_mode: 'turntable', slide_mode: 'turntable',
get_slide_direction(me, level, other) {
return other.direction;
},
on_arrive(me, level, other) { on_arrive(me, level, other) {
level._set_tile_prop(other, 'is_pending_slide', true); level._set_tile_prop(other, 'is_pending_slide', true);
level.set_actor_direction(other, DIRECTIONS[other.direction].right); level.set_actor_direction(other, DIRECTIONS[other.direction].right);
@ -754,6 +754,9 @@ const TILE_TYPES = {
other.type.on_rotate(other, level, 'right'); other.type.on_rotate(other, level, 'right');
} }
}, },
on_stand(me, level, other) {
level.schedule_actor_slide(other);
},
activate(me, level) { activate(me, level) {
level.transmute_tile(me, 'turntable_ccw'); level.transmute_tile(me, 'turntable_ccw');
}, },
@ -765,9 +768,6 @@ const TILE_TYPES = {
contains_wire: true, contains_wire: true,
wire_propagation_mode: 'all', wire_propagation_mode: 'all',
slide_mode: 'turntable', slide_mode: 'turntable',
get_slide_direction(me, level, other) {
return other.direction;
},
on_arrive(me, level, other) { on_arrive(me, level, other) {
level._set_tile_prop(other, 'is_pending_slide', true); level._set_tile_prop(other, 'is_pending_slide', true);
level.set_actor_direction(other, DIRECTIONS[other.direction].left); level.set_actor_direction(other, DIRECTIONS[other.direction].left);
@ -775,6 +775,9 @@ const TILE_TYPES = {
other.type.on_rotate(other, level, 'left'); other.type.on_rotate(other, level, 'left');
} }
}, },
on_stand(me, level, other) {
level.schedule_actor_slide(other);
},
activate(me, level) { activate(me, level) {
level.transmute_tile(me, 'turntable_cw'); level.transmute_tile(me, 'turntable_cw');
}, },
@ -886,12 +889,9 @@ const TILE_TYPES = {
cracked_ice: { cracked_ice: {
layer: LAYERS.terrain, layer: LAYERS.terrain,
slide_mode: 'ice', slide_mode: 'ice',
get_slide_direction(me, level, other) {
return other.direction;
},
speed_factor: 2, speed_factor: 2,
on_arrive(me, level, other) { on_arrive(me, level, other) {
level._set_tile_prop(other, 'is_pending_slide', true); level.schedule_actor_slide(other);
}, },
on_depart(me, level, other) { on_depart(me, level, other) {
level.transmute_tile(me, 'water'); level.transmute_tile(me, 'water');
@ -902,84 +902,73 @@ const TILE_TYPES = {
ice: { ice: {
layer: LAYERS.terrain, layer: LAYERS.terrain,
slide_mode: 'ice', slide_mode: 'ice',
get_slide_direction(me, level, other) {
return other.direction;
},
speed_factor: 2, speed_factor: 2,
on_arrive(me, level, other) { on_arrive(me, level, other) {
level._set_tile_prop(other, 'is_pending_slide', true); level.schedule_actor_slide(other);
}, },
}, },
ice_sw: { ice_sw: {
layer: LAYERS.terrain, layer: LAYERS.terrain,
thin_walls: new Set(['south', 'west']), thin_walls: new Set(['south', 'west']),
slide_mode: 'ice', slide_mode: 'ice',
get_slide_direction(me, level, other) { speed_factor: 2,
return { blocks_leaving: blocks_leaving_thin_walls,
on_arrive(me, level, other) {
let direction = {
north: 'north', north: 'north',
south: 'east', south: 'east',
east: 'east', east: 'east',
west: 'north', west: 'north',
}[other.direction]; }[other.direction];
}, level.schedule_actor_slide(other, direction);
speed_factor: 2,
blocks_leaving: blocks_leaving_thin_walls,
on_arrive(me, level, other) {
level._set_tile_prop(other, 'is_pending_slide', true);
}, },
}, },
ice_nw: { ice_nw: {
layer: LAYERS.terrain, layer: LAYERS.terrain,
thin_walls: new Set(['north', 'west']), thin_walls: new Set(['north', 'west']),
slide_mode: 'ice', slide_mode: 'ice',
get_slide_direction(me, level, other) { speed_factor: 2,
return { blocks_leaving: blocks_leaving_thin_walls,
on_arrive(me, level, other) {
let direction = {
north: 'east', north: 'east',
south: 'south', south: 'south',
east: 'east', east: 'east',
west: 'south', west: 'south',
}[other.direction]; }[other.direction];
}, level.schedule_actor_slide(other, direction);
speed_factor: 2,
blocks_leaving: blocks_leaving_thin_walls,
on_arrive(me, level, other) {
level._set_tile_prop(other, 'is_pending_slide', true);
}, },
}, },
ice_ne: { ice_ne: {
layer: LAYERS.terrain, layer: LAYERS.terrain,
thin_walls: new Set(['north', 'east']), thin_walls: new Set(['north', 'east']),
slide_mode: 'ice', slide_mode: 'ice',
get_slide_direction(me, level, other) { speed_factor: 2,
return { blocks_leaving: blocks_leaving_thin_walls,
on_arrive(me, level, other) {
let direction = {
north: 'west', north: 'west',
south: 'south', south: 'south',
east: 'south', east: 'south',
west: 'west', west: 'west',
}[other.direction]; }[other.direction];
}, level.schedule_actor_slide(other, direction);
speed_factor: 2,
blocks_leaving: blocks_leaving_thin_walls,
on_arrive(me, level, other) {
level._set_tile_prop(other, 'is_pending_slide', true);
}, },
}, },
ice_se: { ice_se: {
layer: LAYERS.terrain, layer: LAYERS.terrain,
thin_walls: new Set(['south', 'east']), thin_walls: new Set(['south', 'east']),
slide_mode: 'ice', slide_mode: 'ice',
get_slide_direction(me, level, other) { speed_factor: 2,
return { blocks_leaving: blocks_leaving_thin_walls,
on_arrive(me, level, other) {
let direction = {
north: 'north', north: 'north',
south: 'west', south: 'west',
east: 'north', east: 'north',
west: 'west', west: 'west',
}[other.direction]; }[other.direction];
}, level.schedule_actor_slide(other, direction);
speed_factor: 2,
blocks_leaving: blocks_leaving_thin_walls,
on_arrive(me, level, other) {
level._set_tile_prop(other, 'is_pending_slide', true);
}, },
}, },
force_floor_n: _define_force_floor('north', 'force_floor_s'), force_floor_n: _define_force_floor('north', 'force_floor_s'),
@ -992,16 +981,25 @@ const TILE_TYPES = {
slide_automatically: true, slide_automatically: true,
speed_factor: 2, speed_factor: 2,
allow_player_override: true, allow_player_override: true,
get_slide_direction(me, level, _other) { blocks(me, level, other) {
return (level.compat.rff_blocks_monsters &&
(other.type.collision_mask & COLLISION.monster_typical));
},
on_stand(me, level, other) {
// XXX this check is necessary because of step_on_cell and then the idle phase causing
// us to be called twice. who is correct?? is the step_on_cell call supposed to be
// there?
if (! other.is_pending_slide) {
level.schedule_actor_slide(other, level.get_force_floor_direction());
}
// FIXME i think it's really that in lynx these push on arrival; try that and see if
// it's better
/*
if (level.compat.force_floors_inert_on_first_tic && level.tic_counter === 0) { if (level.compat.force_floors_inert_on_first_tic && level.tic_counter === 0) {
// Lynx: Force floors don't push on the first tic // Lynx: Force floors don't push on the first tic
return null; return null;
} }
return level.get_force_floor_direction(); */
},
blocks(me, level, other) {
return (level.compat.rff_blocks_monsters &&
(other.type.collision_mask & COLLISION.monster_typical));
}, },
}, },
slime: { slime: {
@ -1726,9 +1724,6 @@ const TILE_TYPES = {
teleport_blue: { teleport_blue: {
layer: LAYERS.terrain, layer: LAYERS.terrain,
slide_mode: 'teleport', slide_mode: 'teleport',
get_slide_direction(me, level, other) {
return other.direction;
},
contains_wire: true, contains_wire: true,
wire_propagation_mode: 'all', wire_propagation_mode: 'all',
*teleport_dest_order(me, level, other) { *teleport_dest_order(me, level, other) {
@ -1816,9 +1811,6 @@ const TILE_TYPES = {
teleport_red: { teleport_red: {
layer: LAYERS.terrain, layer: LAYERS.terrain,
slide_mode: 'teleport', slide_mode: 'teleport',
get_slide_direction(me, level, other) {
return other.direction;
},
contains_wire: true, contains_wire: true,
wire_propagation_mode: 'none', wire_propagation_mode: 'none',
allow_player_override: true, allow_player_override: true,
@ -1873,9 +1865,6 @@ const TILE_TYPES = {
teleport_green: { teleport_green: {
layer: LAYERS.terrain, layer: LAYERS.terrain,
slide_mode: 'teleport', slide_mode: 'teleport',
get_slide_direction(me, level, other) {
return other.direction;
},
*teleport_dest_order(me, level, other) { *teleport_dest_order(me, level, other) {
// The CC2 green teleporter scheme is: // The CC2 green teleporter scheme is:
// 1. Use the PRNG to pick another green teleporter // 1. Use the PRNG to pick another green teleporter
@ -1947,9 +1936,6 @@ const TILE_TYPES = {
layer: LAYERS.terrain, layer: LAYERS.terrain,
item_priority: PICKUP_PRIORITIES.always, item_priority: PICKUP_PRIORITIES.always,
slide_mode: 'teleport', slide_mode: 'teleport',
get_slide_direction(me, level, other) {
return other.direction;
},
allow_player_override: true, allow_player_override: true,
*teleport_dest_order(me, level, other) { *teleport_dest_order(me, level, other) {
let exit_direction = other.direction; let exit_direction = other.direction;