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

@ -93,7 +93,7 @@ export class Tile {
if (this.type.blocks && this.type.blocks(this, level, other, direction)) if (this.type.blocks && this.type.blocks(this, level, other, direction))
return true; return true;
if (other.type.blocked_by && other.type.blocked_by(other, level, this)) if (other.type.blocked_by && other.type.blocked_by(other, level, this))
return true; return true;
@ -116,7 +116,7 @@ export class Tile {
return false; return false;
} }
slide_ignores(name) { slide_ignores(name) {
if (this.type.slide_ignores && this.type.slide_ignores.has(name)) if (this.type.slide_ignores && this.type.slide_ignores.has(name))
return true; return true;
@ -614,7 +614,7 @@ export class Level extends LevelInterface {
// Erase undo, in case any on_ready added to it (we don't want to undo initialization!) // Erase undo, in case any on_ready added to it (we don't want to undo initialization!)
this.pending_undo = this.create_undo_entry(); this.pending_undo = this.create_undo_entry();
} }
connect_button(connectable) { connect_button(connectable) {
let cell = connectable.cell; let cell = connectable.cell;
let x = cell.x; let x = cell.x;
@ -673,11 +673,11 @@ export class Level extends LevelInterface {
break; break;
} }
} }
recalculate_circuitry(first_time = false, undoing = false) { recalculate_circuitry(first_time = false, undoing = false) {
// Build circuits out of connected wires // Build circuits out of connected wires
// TODO document this idea // TODO document this idea
if (!first_time) { if (!first_time) {
for (let circuit of this.circuits) { for (let circuit of this.circuits) {
for (let tile of circuit.tiles) { for (let tile of circuit.tiles) {
@ -685,7 +685,7 @@ export class Level extends LevelInterface {
} }
} }
} }
this.circuits = []; this.circuits = [];
this.power_sources = []; this.power_sources = [];
let wired_outputs = new Set; let wired_outputs = new Set;
@ -714,7 +714,7 @@ export class Level extends LevelInterface {
{ {
wire_directions = actor.wire_directions; wire_directions = actor.wire_directions;
} }
if (! wire_directions && ! terrain.wire_tunnel_directions) { if (! wire_directions && ! terrain.wire_tunnel_directions) {
// No wires, not interesting... unless it's a logic gate, which defines its own // No wires, not interesting... unless it's a logic gate, which defines its own
// wires! We only care about outgoing ones here, on the off chance that they point // wires! We only care about outgoing ones here, on the off chance that they point
@ -812,7 +812,7 @@ export class Level extends LevelInterface {
} }
this.wired_outputs = Array.from(wired_outputs); this.wired_outputs = Array.from(wired_outputs);
this.wired_outputs.sort((a, b) => this.coords_to_scalar(a.cell.x, a.cell.y) - this.coords_to_scalar(b.cell.x, b.cell.y)); this.wired_outputs.sort((a, b) => this.coords_to_scalar(a.cell.x, a.cell.y) - this.coords_to_scalar(b.cell.x, b.cell.y));
if (!first_time) { if (!first_time) {
//update wireables //update wireables
for (var i = 0; i < this.width; ++i) for (var i = 0; i < this.width; ++i)
@ -826,7 +826,7 @@ export class Level extends LevelInterface {
} }
} }
} }
if (!undoing) { if (!undoing) {
this._push_pending_undo(() => this.undid_past_recalculate_circuitry = true); this._push_pending_undo(() => this.undid_past_recalculate_circuitry = true);
} }
@ -1767,7 +1767,7 @@ export class Level extends LevelInterface {
move_to(actor, goal_cell) { move_to(actor, goal_cell) {
if (actor.cell === goal_cell) if (actor.cell === goal_cell)
return; return;
if (actor.type.on_starting_move) { if (actor.type.on_starting_move) {
actor.type.on_starting_move(actor, this); actor.type.on_starting_move(actor, this);
} }
@ -1811,15 +1811,15 @@ export class Level extends LevelInterface {
// Helmet disables this, do nothing // Helmet disables this, do nothing
} }
else if (actor.type.is_real_player && tile.type.is_monster) { 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) { 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) { 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 // Note that blocks squish players if they move for ANY reason, even if pushed by
// another player! The only exception is being pulled // 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) { if (tile.type.on_approach) {
@ -1852,7 +1852,7 @@ export class Level extends LevelInterface {
this.player.movement_cooldown === this.player.movement_speed && this.player.movement_cooldown === this.player.movement_speed &&
! actor.has_item('helmet') && ! this.player.has_item('helmet')) ! 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) { if (this.compat.tiles_react_instantly) {
@ -1865,7 +1865,7 @@ export class Level extends LevelInterface {
if (actor.type.on_finishing_move) { if (actor.type.on_finishing_move) {
actor.type.on_finishing_move(actor, this); actor.type.on_finishing_move(actor, this);
} }
// Step on topmost things first -- notably, it's safe to step on water with flippers on top // Step on topmost things first -- notably, it's safe to step on water with flippers on top
// TODO is there a custom order here similar to collision checking? // TODO is there a custom order here similar to collision checking?
for (let layer = LAYERS.MAX - 1; layer >= 0; layer--) { for (let layer = LAYERS.MAX - 1; layer >= 0; layer--) {
@ -2105,11 +2105,16 @@ export class Level extends LevelInterface {
this.remove_tile(dropping_actor); this.remove_tile(dropping_actor);
this.add_tile(tile, cell); this.add_tile(tile, cell);
if (! this.attempt_out_of_turn_step(tile, dropping_actor.direction)) { 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 // It was unable to move; if it exploded, we have a special non-blocking VFX for
// TODO maybe blow it up with a nonblocking vfx? in cc2 it just vanishes // that, but otherwise there's nothing we can do but erase it (as CC2 does)
this.remove_tile(tile); 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_actor(tile);
} }
this.add_tile(dropping_actor, cell); this.add_tile(dropping_actor, cell);
@ -2130,7 +2135,7 @@ export class Level extends LevelInterface {
this.recalculate_circuitry_next_wire_phase = false; this.recalculate_circuitry_next_wire_phase = false;
force_next_wire_phase = true; force_next_wire_phase = true;
} }
if (this.circuits.length === 0) if (this.circuits.length === 0)
return; return;
@ -2302,7 +2307,7 @@ export class Level extends LevelInterface {
return; return;
} }
} }
//same as above, but accepts multiple tiles //same as above, but accepts multiple tiles
*iter_tiles_in_reading_order_multiple(start_cell, names, reverse = false) { *iter_tiles_in_reading_order_multiple(start_cell, names, reverse = false) {
let i = this.coords_to_scalar(start_cell.x, start_cell.y); let i = this.coords_to_scalar(start_cell.x, start_cell.y);
@ -2422,7 +2427,7 @@ export class Level extends LevelInterface {
} }
this._undo_entry(this.undo_buffer[this.undo_buffer_index]); this._undo_entry(this.undo_buffer[this.undo_buffer_index]);
this.undo_buffer[this.undo_buffer_index] = null; this.undo_buffer[this.undo_buffer_index] = null;
if (this.undid_past_recalculate_circuitry) { if (this.undid_past_recalculate_circuitry) {
this.recalculate_circuitry_next_wire_phase = true; this.recalculate_circuitry_next_wire_phase = true;
this.undid_past_recalculate_circuitry = false; this.undid_past_recalculate_circuitry = false;
@ -2525,13 +2530,40 @@ export class Level extends LevelInterface {
} }
kill_actor(actor, killer, animation_name = null, sfx = null, fail_reason = null) { 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) { if (actor.type.is_real_player) {
// FIXME move death here // Resurrect using the ankh tile, if possible
this.fail(fail_reason, null, actor); 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; return;
} }
if (actor.type.on_death) {
actor.type.on_death(actor, this);
}
if (sfx) { if (sfx) {
this.sfx.play_once(sfx, actor.cell); this.sfx.play_once(sfx, actor.cell);
} }
@ -2550,30 +2582,6 @@ export class Level extends LevelInterface {
if (player === null) { if (player === null) {
player = this.player; 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') { if (reason === 'time') {
this.sfx.play_once('timeup'); this.sfx.play_once('timeup');
@ -2693,8 +2701,8 @@ export class Level extends LevelInterface {
this.add_actor(tile); this.add_actor(tile);
} }
transmute_tile(tile, name) { transmute_tile(tile, name, force = false) {
if (tile.type.ttl) { if (tile.type.ttl && ! force) {
// If this is already an animation, don't turn it into a different one; this can happen // 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 // if a block is pushed onto a cell containing both a mine and slime, both of which try
// to destroy it // to destroy it
@ -2732,10 +2740,7 @@ export class Level extends LevelInterface {
} }
this._init_animation(tile); this._init_animation(tile);
this._set_tile_prop(tile, 'previous_cell', null); this._set_tile_prop(tile, 'previous_cell', null);
} this.make_slide(tile, null);
if (old_type.on_death) {
old_type.on_death(tile, this);
} }
} }

View File

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

View File

@ -820,11 +820,8 @@ const TILE_TYPES = {
})[other.color]); })[other.color]);
level.transmute_tile(other, 'splash'); level.transmute_tile(other, 'splash');
} }
else if (other.type.is_real_player) {
level.fail('drowned', me, other);
}
else { 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') { if (other.type.name === 'dirt_block' || other.type.name === 'ice_block') {
level.transmute_tile(me, 'floor'); level.transmute_tile(me, 'floor');
} }
else if (other.type.is_real_player) {
level.fail('slimed', me, other);
}
else { 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) { on_arrive(me, level, other) {
level.remove_tile(me); level.remove_tile(me);
if (other.type.is_real_player) { level.kill_actor(other, me, 'explosion', 'bomb', 'exploded');
level.fail('exploded', me, other);
}
else {
level.sfx.play_once('bomb', me.cell);
level.transmute_tile(other, 'explosion');
}
}, },
}, },
hole: { hole: {
@ -1072,12 +1060,7 @@ const TILE_TYPES = {
} }
}, },
on_arrive(me, level, other) { on_arrive(me, level, other) {
if (other.type.is_real_player) { level.kill_actor(other, me, 'fall', null, 'fell');
level.fail('fell', me, other);
}
else {
level.transmute_tile(other, 'fall');
}
}, },
visual_state(me) { visual_state(me) {
return (me && me.visual_state) ?? 'open'; return (me && me.visual_state) ?? 'open';
@ -1087,18 +1070,17 @@ const TILE_TYPES = {
layer: LAYERS.terrain, layer: LAYERS.terrain,
on_depart(me, level, other) { on_depart(me, level, other) {
level.spawn_animation(me.cell, 'puff'); level.spawn_animation(me.cell, 'puff');
level.transmute_tile(me, 'hole');
if (other === level.player) { if (other === level.player) {
level.sfx.play_once('popwall', me.cell); level.sfx.play_once('popwall', me.cell);
} }
},
on_death(me, level) { level.transmute_tile(me, 'hole');
//update hole visual state // Update hole visual state (note that me.type is hole now)
me.type.on_begin(me, level); me.type.on_begin(me, level);
var one_south = level.cell(me.cell.x, me.cell.y + 1); 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); me.type.on_begin(one_south.get_terrain(), level);
} }
}, },
}, },
thief_tools: { thief_tools: {
@ -1358,13 +1340,7 @@ const TILE_TYPES = {
is_required_chip: true, is_required_chip: true,
on_arrive(me, level, other) { on_arrive(me, level, other) {
level.remove_tile(me); level.remove_tile(me);
if (other.type.is_real_player) { level.kill_actor(other, me, 'explosion', 'bomb', 'exploded');
level.fail('exploded', me, other);
}
else {
level.sfx.play_once('bomb', me.cell);
level.transmute_tile(other, 'explosion');
}
}, },
// Not affected by gray buttons // Not affected by gray buttons
}, },
@ -1508,7 +1484,17 @@ const TILE_TYPES = {
actor._clone_release = true; actor._clone_release = true;
// Wire activation allows the cloner to try every direction, searching clockwise // Wire activation allows the cloner to try every direction, searching clockwise
for (let i = 0; i < (aggressive ? 4 : 1); i++) { 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 // Surprising edge case: if the actor immediately killed the player, do NOT
// spawn a new template, since the move was actually aborted // spawn a new template, since the move was actually aborted
// FIXME this is inconsistent. the move was aborted because of an emergency // 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 // FIXME add this underneath, just above the cloner, so the new actor is on top
let new_template = new actor.constructor(type, direction); let new_template = new actor.constructor(type, direction);
// TODO maybe make a type method for this
if (type.on_clone) { if (type.on_clone) {
type.on_clone(new_template, actor); 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 // 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 // TODO would be neat if this understood "ignores anything with fire immunity" but that
// might be a bit too high-level for this game // might be a bit too high-level for this game
if (other.type.is_real_player) { level.kill_actor(other, me, 'explosion', 'bomb', 'burned');
level.fail('burned', me, other);
}
else {
level.sfx.play_once('bomb', me.cell);
level.transmute_tile(other, 'explosion');
}
}, },
}, },
electrified_floor: { electrified_floor: {
@ -1952,13 +1931,7 @@ const TILE_TYPES = {
if (! me.is_active) if (! me.is_active)
return; return;
if (other.type.is_real_player) { level.kill_actor(other, me, 'explosion', 'bomb', 'electrocuted');
level.fail('electrocuted', me, other);
}
else {
level.sfx.play_once('bomb', me.cell);
level.transmute_tile(other, 'explosion');
}
}, },
on_power(me, level) { on_power(me, level) {
level._set_tile_prop(me, 'is_active', true); level._set_tile_prop(me, 'is_active', true);
@ -1967,6 +1940,7 @@ const TILE_TYPES = {
level._set_tile_prop(me, 'is_active', false); level._set_tile_prop(me, 'is_active', false);
}, },
on_death(me, level) { on_death(me, level) {
// FIXME i probably broke this lol
//need to remove our wires since they're an implementation detail //need to remove our wires since they're an implementation detail
level._set_tile_prop(me, 'wire_directions', 0); level._set_tile_prop(me, 'wire_directions', 0);
level.recalculate_circuitry_next_wire_phase = true; level.recalculate_circuitry_next_wire_phase = true;
@ -2758,40 +2732,31 @@ const TILE_TYPES = {
let actor = cell.get_actor(); let actor = cell.get_actor();
let terrain = cell.get_terrain(); let terrain = cell.get_terrain();
let item = cell.get_item();
let removed_anything; let removed_anything;
for (let layer = LAYERS.MAX - 1; layer >= 0; layer--) { for (let layer = LAYERS.MAX - 1; layer >= 0; layer--) {
let tile = cell[layer]; let tile = cell[layer];
if (! tile) if (! tile)
continue; continue;
if (tile.type.layer === LAYERS.terrain) { // Canopy protects everything else
// 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') { if (tile.type.name === 'canopy') {
// Canopy protects everything else
actor = null; actor = null;
terrain = null; terrain = null;
break; 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) { if (actor) {
@ -2807,25 +2772,29 @@ const TILE_TYPES = {
} }
else if (terrain) { else if (terrain) {
// Anything other than these babies gets blown up and turned into floor // 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' ||
//do nothing 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') { else if (terrain.type.name === 'cracked_floor') {
level.transmute_tile(terrain, 'hole'); level.transmute_tile(terrain, 'hole');
removed_anything = true; removed_anything = true;
} }
else if (!( else {
terrain.type.name === 'steel' || terrain.type.name === 'socket' ||
terrain.type.name === 'logic_gate' || terrain.type.name === 'floor'))
{
level.transmute_tile(terrain, 'floor'); level.transmute_tile(terrain, 'floor');
removed_anything = true; removed_anything = true;
} }
} }
// TODO maybe add a vfx nonblocking explosion if (removed_anything || actor) {
if (removed_anything && ! cell.get_actor()) { if (actor) {
level.spawn_animation(cell, 'explosion'); 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, is_monster: true,
can_reveal_walls: true, can_reveal_walls: true,
collision_mask: COLLISION.bowling_ball, collision_mask: COLLISION.bowling_ball,
blocks_collision: COLLISION.bowling_ball,
item_pickup_priority: PICKUP_PRIORITIES.normal, item_pickup_priority: PICKUP_PRIORITIES.normal,
// FIXME do i start moving immediately when dropped, or next turn? // FIXME do i start moving immediately when dropped, or next turn?
movement_speed: 4, movement_speed: 4,
@ -2856,38 +2826,22 @@ const TILE_TYPES = {
return [me.direction]; return [me.direction];
}, },
on_approach(me, level, other) { on_approach(me, level, other) {
// Blow up anything that runs into us... unless we're on a cloner // Blow up anything that runs into us
// FIXME there are other cases where this won't be right; this shouldn't happen if the level.kill_actor(other, me, 'explosion');
// cell blocks the actor, but i don't have a callback for that? level.kill_actor(me, me, 'explosion', 'bomb');
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');
}, },
on_blocked(me, level, direction, obstacle) { on_blocked(me, level, direction, obstacle) {
// Blow up anything we run into // Blow up anything we run into
if (obstacle && obstacle.type.is_actor) { if (obstacle && obstacle.type.is_actor) {
if (obstacle.type.is_real_player) { level.kill_actor(obstacle, me, 'explosion');
level.fail(me.type.name, me, obstacle);
}
else {
level.transmute_tile(obstacle, 'explosion');
}
} }
else if (me.slide_mode) { else if (me.slide_mode || me._clone_release) {
// Sliding bowling balls don't blow up if they hit a regular wall // 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; return;
} }
level.sfx.play_once('bomb', me.cell); level.sfx.play_once('bomb', me.cell);
level.transmute_tile(me, 'explosion'); 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: { xray_eye: {
@ -3205,6 +3159,14 @@ const TILE_TYPES = {
level.remove_tile(me); 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 // Used as an easy way to show an invisible wall when bumped
wall_invisible_revealed: { wall_invisible_revealed: {
layer: LAYERS.vfx, layer: LAYERS.vfx,