diff --git a/js/defs.js b/js/defs.js index ba502da..f870883 100644 --- a/js/defs.js +++ b/js/defs.js @@ -150,6 +150,10 @@ export const COMPAT_FLAG_CATEGORIES = [{ key: 'no_auto_convert_ccl_blue_walls', label: "Blue walls under blocks are not auto-converted in CCL levels", rulesets: new Set(['steam-strict', 'lynx', 'ms']), + }, { + key: 'no_auto_convert_ccl_bombs', + label: "Mines under actors are not auto-converted in CCL levels", + rulesets: new Set(['steam-strict', 'lynx', 'ms']), }], }, { title: "Actor behavior", @@ -300,6 +304,7 @@ export const COMPAT_FLAG_CATEGORIES = [{ label: "Bowling balls on cloners are destroyed when fired at point blank", rulesets: new Set(['steam-strict']), }, { + // XXX is this necessary, with the addition of the dormant bomb? key: 'bombs_immediately_detonate_under_players', label: "Mines under players detonate when the level starts", rulesets: new Set(['steam-strict']), diff --git a/js/tileset.js b/js/tileset.js index aa1fcd6..1dabf29 100644 --- a/js/tileset.js +++ b/js/tileset.js @@ -142,6 +142,7 @@ export const CC2_TILESET_LAYOUT = { bomb: [5, 4], fuse: [7, 4], }, + dormant_bomb: [5, 4], // compat tile, so needs a fallback green_bomb: { __special__: 'bomb-fuse', bomb: [6, 4], @@ -309,7 +310,7 @@ export const CC2_TILESET_LAYOUT = { }, }, popwall: [8, 10], - popwall2: [8, 10], + popwall2: [8, 10], // compat tile, so needs a fallback gravel: [9, 10], ball: { __special__: 'animated', @@ -897,6 +898,7 @@ export const TILE_WORLD_TILESET_LAYOUT = { button_blue: [2, 8], teleport_blue: [2, 9], bomb: [2, 10], + dormant_bomb: [2, 10], // compat tile, so needs a fallback trap: { __special__: 'visual-state', closed: [2, 11], @@ -905,7 +907,7 @@ export const TILE_WORLD_TILESET_LAYOUT = { wall_appearing: [2, 12], gravel: [2, 13], popwall: [2, 14], - popwall2: [2, 14], + popwall2: [2, 14], // compat tile, so needs a fallback hint: [2, 15], cloner: [3, 1], @@ -1335,9 +1337,13 @@ export const LL_TILESET_LAYOUT = { foil: [2, 17], xray_eye: [3, 17], helmet: [4, 17], + phantom_ring: [5, 17], + feather: [6, 17], + dormant_bomb: [7, 17], skeleton_key: [0, 18], ankh: [1, 18], floor_ankh: [2, 18], + toll_gate: [5, 18], no_sign: [6, 18], gift_bow: [7, 18], score_10: [0, 19], @@ -3070,6 +3076,10 @@ export function parse_tile_world_large_tileset(canvas) { } ctx.putImageData(image_data, 0, 0); + // These are compat tiles, which need to have a fallback + layout['popwall2'] = layout['popwall']; + layout['dormant_bomb'] = layout['bomb']; + return new Tileset(canvas, layout, tw, th); } diff --git a/js/tiletypes.js b/js/tiletypes.js index ea1bee1..9c734ca 100644 --- a/js/tiletypes.js +++ b/js/tiletypes.js @@ -352,8 +352,8 @@ const TILE_TYPES = { level.stored_level.format === 'ccl' && me.cell.get_actor()) { - // Fix blocks and other actors on top of popwalls by turning them into double - // popwalls, which preserves CC2 popwall behavior + // CCL: Actors who start on popwalls are not intended to activate them when they + // leave, so preserve CC2 behavior by changing them to double popwalls me.type = TILE_TYPES['popwall2']; } }, @@ -430,8 +430,8 @@ const TILE_TYPES = { level.stored_level.format === 'ccl' && me.cell.get_actor()) { - // Blocks can be pushed off of blue walls in TW Lynx, which only works due to a tiny - // quirk of the engine that I don't want to replicate, so replace them with popwalls + // CCL: Blocks can be pushed off of blue walls in TW Lynx, but we can replicate the + // behavior with CC2 rules by replacing them with popwalls // TODO this also works with invis walls apparently. maybe only for blocks? me.type = TILE_TYPES['popwall']; } @@ -1062,6 +1062,17 @@ const TILE_TYPES = { }, bomb: { layer: LAYERS.item, + on_ready(me, level) { + if (! level.compat.no_auto_convert_ccl_bombs && + level.stored_level.format === 'ccl' && + me.cell.get_actor()) + { + // CCL: A number of custom levels start an actor on top of a bomb (I guess to + // conserve space), relying on the CC1 behavior that bombs only detonate when + // stepped onto. Replace those with the custom "dormant bomb" tile. + me.type = TILE_TYPES['dormant_bomb']; + } + }, on_arrive(me, level, other) { if (level.compat.bombs_detonate_on_arrive) { me.type._detonate(me, level, other); @@ -1083,6 +1094,18 @@ const TILE_TYPES = { level.kill_actor(other, me, 'explosion', 'bomb', 'exploded'); }, }, + // Bomb variant originally added as a CC1 autofix, but which doubles as an experimental item -- + // it's dormant until you drop it and move off of it, at which point it becomes a normal bomb + dormant_bomb: { + ...COMMON_TOOL, + on_depart(me, level, other) { + // Unlike dynamite, anyone can activate this (important to make it work as CCL compat) + if (me.cell.get_item_mod()) + return; + + level.transmute_tile(me, 'bomb'); + }, + }, hole: { layer: LAYERS.terrain, on_ready(me, level) { @@ -2800,24 +2823,28 @@ const TILE_TYPES = { dynamite: { ...COMMON_TOOL, on_depart(me, level, other) { - if (other.type.is_real_player && ! me.cell.get_item_mod()) { - // FIXME wiki just says about 4.3 seconds; more likely this is exactly 255 frames - level._set_tile_prop(me, 'timer', 85); - level.transmute_tile(me, 'dynamite_lit'); - // Actors are expected to have this, so populate it - level._set_tile_prop(me, 'movement_cooldown', 0); - level.add_actor(me); - // Dynamite inherits a copy of the player's inventory, which largely doesn't matter - // except for suction boots, helmet, or lightning bolt; keys can't matter because - // dynamite is blocked by doors - if (other.toolbelt) { - level._set_tile_prop(me, 'toolbelt', [...other.toolbelt]); - } - // Dynamite that lands on a force floor is moved by it, and dynamite that lands on a - // button holds it down - // TODO is there anything this should NOT activate? - level.step_on_cell(me, me.cell); + if (! other.type.is_real_player) + return; + if (me.cell.get_item_mod()) + return; + + // XXX wiki just says about 4.3 seconds; more likely this is exactly 255 frames (and + // there haven't been any compat problems so far...) + level._set_tile_prop(me, 'timer', 85); + level.transmute_tile(me, 'dynamite_lit'); + // Actors are expected to have this, so populate it + level._set_tile_prop(me, 'movement_cooldown', 0); + level.add_actor(me); + // Dynamite inherits a copy of the player's inventory, which largely doesn't matter + // except for suction boots, helmet, or lightning bolt; keys can't matter because + // dynamite is blocked by doors + if (other.toolbelt) { + level._set_tile_prop(me, 'toolbelt', [...other.toolbelt]); } + // Dynamite that lands on a force floor is moved by it, and dynamite that lands on a + // button holds it down + // TODO is there anything this should NOT activate? + level.step_on_cell(me, me.cell); }, }, dynamite_lit: { diff --git a/tileset-lexy.png b/tileset-lexy.png index 093bd9b..65e0389 100644 Binary files a/tileset-lexy.png and b/tileset-lexy.png differ diff --git a/tileset-src/tileset-lexy.aseprite b/tileset-src/tileset-lexy.aseprite index 8ce2328..43aa211 100644 Binary files a/tileset-src/tileset-lexy.aseprite and b/tileset-src/tileset-lexy.aseprite differ