Use kill_actor when appropriate; fix some bowling ball behavior
This commit is contained in:
parent
2cf6afa590
commit
1e5160b40d
87
js/game.js
87
js/game.js
@ -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);
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (tile.cell) {
|
||||
this.add_actor(tile);
|
||||
}
|
||||
this.add_tile(dropping_actor, cell);
|
||||
@ -2525,13 +2530,40 @@ 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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -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]],
|
||||
|
||||
178
js/tiletypes.js
178
js/tiletypes.js
@ -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,18 +1070,17 @@ 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
|
||||
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') {
|
||||
me.type.on_begin(one_south.get_terrain(), level);
|
||||
}
|
||||
|
||||
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 && one_south.get_terrain().type.name === 'hole') {
|
||||
me.type.on_begin(one_south.get_terrain(), level);
|
||||
}
|
||||
},
|
||||
},
|
||||
thief_tools: {
|
||||
@ -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;
|
||||
}
|
||||
|
||||
// Canopy protects everything else
|
||||
if (tile.type.name === 'canopy') {
|
||||
// Canopy protects everything else
|
||||
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') {
|
||||
//do nothing
|
||||
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);
|
||||
}
|
||||
else {
|
||||
level.transmute_tile(obstacle, 'explosion');
|
||||
}
|
||||
level.kill_actor(obstacle, me, '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,
|
||||
|
||||
Loading…
Reference in New Issue
Block a user