Use kill_actor when appropriate; fix some bowling ball behavior

This commit is contained in:
Eevee (Evelyn Woods) 2021-03-08 20:02:23 -07:00
parent 2cf6afa590
commit 1e5160b40d
3 changed files with 133 additions and 163 deletions

View File

@ -1811,15 +1811,15 @@ export class Level extends LevelInterface {
// Helmet disables this, do nothing
}
else if (actor.type.is_real_player && tile.type.is_monster) {
this.fail(tile.type.name, tile, actor);
this.kill_actor(actor, tile);
}
else if (actor.type.is_monster && tile.type.is_real_player) {
this.fail(actor.type.name, actor, tile);
this.kill_actor(tile, actor);
}
else if (actor.type.is_block && tile.type.is_real_player && ! actor.is_pulled) {
// Note that blocks squish players if they move for ANY reason, even if pushed by
// another player! The only exception is being pulled
this.fail('squished', actor, tile);
this.kill_actor(tile, actor, null, null, 'squished');
}
if (tile.type.on_approach) {
@ -1852,7 +1852,7 @@ export class Level extends LevelInterface {
this.player.movement_cooldown === this.player.movement_speed &&
! actor.has_item('helmet') && ! this.player.has_item('helmet'))
{
this.fail(actor.type.name, actor, this.player);
this.kill_actor(this.player, actor);
}
if (this.compat.tiles_react_instantly) {
@ -2105,11 +2105,16 @@ export class Level extends LevelInterface {
this.remove_tile(dropping_actor);
this.add_tile(tile, cell);
if (! this.attempt_out_of_turn_step(tile, dropping_actor.direction)) {
// It was unable to move, so there's nothing we can do but destroy it
// TODO maybe blow it up with a nonblocking vfx? in cc2 it just vanishes
this.remove_tile(tile);
// It was unable to move; if it exploded, we have a special non-blocking VFX for
// that, but otherwise there's nothing we can do but erase it (as CC2 does)
if (tile.type.name === 'explosion') {
this.transmute_tile(tile, 'explosion_nb', true);
}
else {
this.remove_tile(tile);
}
}
if (tile.cell) {
this.add_actor(tile);
}
this.add_tile(dropping_actor, cell);
@ -2525,12 +2530,39 @@ export class Level extends LevelInterface {
}
kill_actor(actor, killer, animation_name = null, sfx = null, fail_reason = null) {
// FIXME use this everywhere, fail when it's a player, move on_death here
if (actor.type.is_real_player) {
// FIXME move death here
this.fail(fail_reason, null, actor);
// Resurrect using the ankh tile, if possible
if (this.ankh_tile) {
let ankh_cell = this.ankh_tile.cell;
let existing_actor = ankh_cell.get_actor();
if (! existing_actor) {
// FIXME water should still splash, etc
this.sfx.play_once('revive');
this._set_tile_prop(actor, 'movement_cooldown', null);
this._set_tile_prop(actor, 'movement_speed', null);
this.make_slide(actor, null);
this.move_to(actor, ankh_cell);
this.transmute_tile(this.ankh_tile, 'floor');
this.spawn_animation(ankh_cell, 'resurrection');
let old_tile = this.ankh_tile;
this.ankh_tile = null;
this._push_pending_undo(() => {
this.ankh_tile = old_tile;
});
return;
}
}
// Otherwise, lose the game
this.fail(fail_reason || killer.type.name, null, actor);
return;
}
if (actor.type.on_death) {
actor.type.on_death(actor, this);
}
if (sfx) {
this.sfx.play_once(sfx, actor.cell);
@ -2551,30 +2583,6 @@ export class Level extends LevelInterface {
player = this.player;
}
// FIXME move to kill_actor
if (player != null && this.ankh_tile && reason !== 'time') {
let cell = this.ankh_tile.cell;
let actor = cell.get_actor();
if (! actor) {
// FIXME water should still splash, etc
this.sfx.play_once('revive');
this._set_tile_prop(player, 'movement_cooldown', null);
this._set_tile_prop(player, 'movement_speed', null);
this.make_slide(player, null);
this.move_to(player, cell);
this.transmute_tile(this.ankh_tile, 'floor');
this.spawn_animation(cell, 'resurrection');
let old_tile = this.ankh_tile;
this.ankh_tile = null;
this._push_pending_undo(() => {
this.ankh_tile = old_tile;
});
return;
}
}
if (reason === 'time') {
this.sfx.play_once('timeup');
}
@ -2693,8 +2701,8 @@ export class Level extends LevelInterface {
this.add_actor(tile);
}
transmute_tile(tile, name) {
if (tile.type.ttl) {
transmute_tile(tile, name, force = false) {
if (tile.type.ttl && ! force) {
// If this is already an animation, don't turn it into a different one; this can happen
// if a block is pushed onto a cell containing both a mine and slime, both of which try
// to destroy it
@ -2732,10 +2740,7 @@ export class Level extends LevelInterface {
}
this._init_animation(tile);
this._set_tile_prop(tile, 'previous_cell', null);
}
if (old_type.on_death) {
old_type.on_death(tile, this);
this.make_slide(tile, null);
}
}

View File

@ -151,6 +151,7 @@ export const CC2_TILESET_LAYOUT = {
wall_custom_blue: [15, 4],
explosion: [[0, 5], [1, 5], [2, 5], [3, 5]],
explosion_nb: [[0, 5], [1, 5], [2, 5], [3, 5]],
splash_slime: [[0, 5], [1, 5], [2, 5], [3, 5]],
splash: [[4, 5], [5, 5], [6, 5], [7, 5]],
flame_jet_off: [8, 5],
@ -900,6 +901,7 @@ export const TILE_WORLD_TILESET_LAYOUT = {
bogus_player_burned_fire: [3, 4],
bogus_player_burned: [3, 5],
explosion: [3, 6],
explosion_nb: [3, 6],
explosion_other: [3, 7], // TODO ???
// 3, 8 unused
bogus_player_win: [3, 9], // TODO 10 and 11 too? does this animate?
@ -1971,6 +1973,7 @@ export const LL_TILESET_LAYOUT = {
// VFX
explosion: [[16, 26], [17, 26], [18, 26], [19, 26]],
explosion_nb: [[16, 26], [17, 26], [18, 26], [19, 26]],
splash: [[16, 27], [17, 27], [18, 27], [19, 27]],
splash_slime: [[16, 28], [17, 28], [18, 28], [19, 28]],
fall: [[16, 29], [17, 29], [18, 29], [19, 29]],

View File

@ -820,11 +820,8 @@ const TILE_TYPES = {
})[other.color]);
level.transmute_tile(other, 'splash');
}
else if (other.type.is_real_player) {
level.fail('drowned', me, other);
}
else {
level.transmute_tile(other, 'splash');
level.kill_actor(other, me, 'splash', null, 'drowned');
}
},
},
@ -1027,11 +1024,8 @@ const TILE_TYPES = {
if (other.type.name === 'dirt_block' || other.type.name === 'ice_block') {
level.transmute_tile(me, 'floor');
}
else if (other.type.is_real_player) {
level.fail('slimed', me, other);
}
else {
level.transmute_tile(other, 'splash_slime');
level.kill_actor(other, me, 'splash_slime', null, 'slimed');
}
},
},
@ -1051,13 +1045,7 @@ const TILE_TYPES = {
},
on_arrive(me, level, other) {
level.remove_tile(me);
if (other.type.is_real_player) {
level.fail('exploded', me, other);
}
else {
level.sfx.play_once('bomb', me.cell);
level.transmute_tile(other, 'explosion');
}
level.kill_actor(other, me, 'explosion', 'bomb', 'exploded');
},
},
hole: {
@ -1072,12 +1060,7 @@ const TILE_TYPES = {
}
},
on_arrive(me, level, other) {
if (other.type.is_real_player) {
level.fail('fell', me, other);
}
else {
level.transmute_tile(other, 'fall');
}
level.kill_actor(other, me, 'fall', null, 'fell');
},
visual_state(me) {
return (me && me.visual_state) ?? 'open';
@ -1087,16 +1070,15 @@ const TILE_TYPES = {
layer: LAYERS.terrain,
on_depart(me, level, other) {
level.spawn_animation(me.cell, 'puff');
level.transmute_tile(me, 'hole');
if (other === level.player) {
level.sfx.play_once('popwall', me.cell);
}
},
on_death(me, level) {
//update hole visual state
level.transmute_tile(me, 'hole');
// Update hole visual state (note that me.type is hole now)
me.type.on_begin(me, level);
var one_south = level.cell(me.cell.x, me.cell.y + 1);
if (one_south !== null && one_south.get_terrain().type.name == 'hole') {
if (one_south && one_south.get_terrain().type.name === 'hole') {
me.type.on_begin(one_south.get_terrain(), level);
}
},
@ -1358,13 +1340,7 @@ const TILE_TYPES = {
is_required_chip: true,
on_arrive(me, level, other) {
level.remove_tile(me);
if (other.type.is_real_player) {
level.fail('exploded', me, other);
}
else {
level.sfx.play_once('bomb', me.cell);
level.transmute_tile(other, 'explosion');
}
level.kill_actor(other, me, 'explosion', 'bomb', 'exploded');
},
// Not affected by gray buttons
},
@ -1508,7 +1484,17 @@ const TILE_TYPES = {
actor._clone_release = true;
// Wire activation allows the cloner to try every direction, searching clockwise
for (let i = 0; i < (aggressive ? 4 : 1); i++) {
if (level.attempt_out_of_turn_step(actor, direction)) {
// If the actor successfully moves, replace it with a new clone. As a special case,
// bowling balls that immediately destroy something are also considered to have
// successfully exited
let success = level.attempt_out_of_turn_step(actor, direction);
if (! success && actor.type.ttl && ! level.compat.cloned_bowling_balls_can_be_lost) {
success = true;
if (actor.type.layer === LAYERS.actor) {
level.transmute_tile(actor, 'explosion_nb', true);
}
}
if (success) {
// Surprising edge case: if the actor immediately killed the player, do NOT
// spawn a new template, since the move was actually aborted
// FIXME this is inconsistent. the move was aborted because of an emergency
@ -1519,7 +1505,6 @@ const TILE_TYPES = {
// FIXME add this underneath, just above the cloner, so the new actor is on top
let new_template = new actor.constructor(type, direction);
// TODO maybe make a type method for this
if (type.on_clone) {
type.on_clone(new_template, actor);
}
@ -1932,13 +1917,7 @@ const TILE_TYPES = {
// Note that (dirt?) blocks, fireballs, and anything with fire boots are immune
// TODO would be neat if this understood "ignores anything with fire immunity" but that
// might be a bit too high-level for this game
if (other.type.is_real_player) {
level.fail('burned', me, other);
}
else {
level.sfx.play_once('bomb', me.cell);
level.transmute_tile(other, 'explosion');
}
level.kill_actor(other, me, 'explosion', 'bomb', 'burned');
},
},
electrified_floor: {
@ -1952,13 +1931,7 @@ const TILE_TYPES = {
if (! me.is_active)
return;
if (other.type.is_real_player) {
level.fail('electrocuted', me, other);
}
else {
level.sfx.play_once('bomb', me.cell);
level.transmute_tile(other, 'explosion');
}
level.kill_actor(other, me, 'explosion', 'bomb', 'electrocuted');
},
on_power(me, level) {
level._set_tile_prop(me, 'is_active', true);
@ -1967,6 +1940,7 @@ const TILE_TYPES = {
level._set_tile_prop(me, 'is_active', false);
},
on_death(me, level) {
// FIXME i probably broke this lol
//need to remove our wires since they're an implementation detail
level._set_tile_prop(me, 'wire_directions', 0);
level.recalculate_circuitry_next_wire_phase = true;
@ -2758,40 +2732,31 @@ const TILE_TYPES = {
let actor = cell.get_actor();
let terrain = cell.get_terrain();
let item = cell.get_item();
let removed_anything;
for (let layer = LAYERS.MAX - 1; layer >= 0; layer--) {
let tile = cell[layer];
if (! tile)
continue;
if (tile.type.layer === LAYERS.terrain) {
// Terrain gets transmuted afterwards
}
else if (tile.type.is_real_player) {
// TODO it would be nice if i didn't have to special-case this every
// time
level.fail(me.type.name, me, tile);
}
else {
//newly appearing items (e.g. dropped by a glass block) are safe
if (tile.type.layer === LAYERS.item && tile !== item) {
continue;
}
// Everything else is destroyed
if (tile.type.on_death) {
tile.type.on_death(tile, level);
}
level.remove_tile(tile);
removed_anything = true;
}
if (tile.type.name === 'canopy') {
// Canopy protects everything else
if (tile.type.name === 'canopy') {
actor = null;
terrain = null;
break;
}
// Terrain is transmuted afterwards; VFX are left alone; actors are killed
// after the loop (which also allows the glass block to safely drop an item)
if (tile.type.layer === LAYERS.terrain ||
tile.type.layer === LAYERS.actor ||
tile.type.layer === LAYERS.vfx)
{
continue;
}
// Anything else is destroyed
level.remove_tile(tile);
removed_anything = true;
}
if (actor) {
@ -2807,25 +2772,29 @@ const TILE_TYPES = {
}
else if (terrain) {
// Anything other than these babies gets blown up and turned into floor
if (terrain.type.name === 'hole') {
if (terrain.type.name === 'steel' || terrain.type.name === 'socket' ||
terrain.type.name === 'logic_gate' || terrain.type.name === 'floor' ||
terrain.type.name === 'hole' || terrain.type.name === 'floor_ankh')
{
// do nothing
}
else if (terrain.type.name === 'cracked_floor') {
level.transmute_tile(terrain, 'hole');
removed_anything = true;
}
else if (!(
terrain.type.name === 'steel' || terrain.type.name === 'socket' ||
terrain.type.name === 'logic_gate' || terrain.type.name === 'floor'))
{
else {
level.transmute_tile(terrain, 'floor');
removed_anything = true;
}
}
// TODO maybe add a vfx nonblocking explosion
if (removed_anything && ! cell.get_actor()) {
level.spawn_animation(cell, 'explosion');
if (removed_anything || actor) {
if (actor) {
level.kill_actor(actor, me, 'explosion');
}
else {
level.spawn_animation(cell, cell.get_actor() ? 'explosion_nb' : 'explosion');
}
}
}
}
@ -2849,6 +2818,7 @@ const TILE_TYPES = {
is_monster: true,
can_reveal_walls: true,
collision_mask: COLLISION.bowling_ball,
blocks_collision: COLLISION.bowling_ball,
item_pickup_priority: PICKUP_PRIORITIES.normal,
// FIXME do i start moving immediately when dropped, or next turn?
movement_speed: 4,
@ -2856,38 +2826,22 @@ const TILE_TYPES = {
return [me.direction];
},
on_approach(me, level, other) {
// Blow up anything that runs into us... unless we're on a cloner
// FIXME there are other cases where this won't be right; this shouldn't happen if the
// cell blocks the actor, but i don't have a callback for that?
if (me.cell.has('cloner'))
return;
if (other.type.is_real_player) {
level.fail(me.type.name, me, other);
}
else {
level.transmute_tile(other, 'explosion');
}
level.sfx.play_once('bomb', me.cell);
level.transmute_tile(me, 'explosion');
// Blow up anything that runs into us
level.kill_actor(other, me, 'explosion');
level.kill_actor(me, me, 'explosion', 'bomb');
},
on_blocked(me, level, direction, obstacle) {
// Blow up anything we run into
if (obstacle && obstacle.type.is_actor) {
if (obstacle.type.is_real_player) {
level.fail(me.type.name, me, obstacle);
level.kill_actor(obstacle, me, 'explosion');
}
else {
level.transmute_tile(obstacle, 'explosion');
}
}
else if (me.slide_mode) {
// Sliding bowling balls don't blow up if they hit a regular wall
else if (me.slide_mode || me._clone_release) {
// Sliding bowling balls don't blow up if they hit a regular wall, and neither do
// bowling balls in the process of being released from a cloner
return;
}
level.sfx.play_once('bomb', me.cell);
level.transmute_tile(me, 'explosion');
// Remove our slide mode so we don't attempt to bounce if on ice
level.make_slide(me, null);
},
},
xray_eye: {
@ -3205,6 +3159,14 @@ const TILE_TYPES = {
level.remove_tile(me);
},
},
// Non-blocking explosion used for better handling edge cases with dynamite and bowling balls,
// without changing gameplay
explosion_nb: {
layer: LAYERS.vfx,
is_actor: true,
collision_mask: 0,
ttl: 16,
},
// Used as an easy way to show an invisible wall when bumped
wall_invisible_revealed: {
layer: LAYERS.vfx,