export const TICS_PER_SECOND = 20; export const DIRECTIONS = { north: { movement: [0, -1], bit: 0x01, opposite_bit: 0x04, index: 0, action: 'up', left: 'west', right: 'east', opposite: 'south', mirrored: 'north', flipped: 'south', }, south: { movement: [0, 1], bit: 0x04, opposite_bit: 0x01, index: 2, action: 'down', left: 'east', right: 'west', opposite: 'north', mirrored: 'south', flipped: 'north', }, west: { movement: [-1, 0], bit: 0x08, opposite_bit: 0x02, index: 3, action: 'left', left: 'south', right: 'north', opposite: 'east', mirrored: 'east', flipped: 'west', }, east: { movement: [1, 0], bit: 0x02, opposite_bit: 0x08, index: 1, action: 'right', left: 'north', right: 'south', opposite: 'west', mirrored: 'west', flipped: 'east', }, }; // Should match the bit ordering above, and CC2's order export const DIRECTION_ORDER = ['north', 'east', 'south', 'west']; export const INPUT_BITS = { drop: 0x01, down: 0x02, left: 0x04, right: 0x08, up: 0x10, swap: 0x20, cycle: 0x40, // Not real input; used to force advancement for turn-based mode wait: 0x8000, }; export const LAYERS = { terrain: 0, item: 1, item_mod: 2, actor: 3, vfx: 4, swivel: 5, thin_wall: 6, canopy: 7, MAX: 8, }; export const COLLISION = { real_player1: 0x0001, real_player2: 0x0002, real_player: 0x0003, doppel1: 0x0004, doppel2: 0x0008, doppel: 0x000c, playerlike1: 0x0005, playerlike2: 0x000a, playerlike: 0x000f, block_cc1: 0x0010, block_cc2: 0x0020, // ice + frame (+ circuit, etc) bowling_ball: 0x0040, // rolling ball, dynamite // Monsters are a little complicated, because some of them have special rules, e.g. fireballs // aren't blocked by fire. // For a monster's MASK, you should use ONLY ONE of these specific monster bits (if // appropriate), OR the generic bit -- DO NOT combine them! monster_generic: 0x0100, fireball: 0x0200, bug: 0x0400, yellow_tank: 0x0800, rover: 0x1000, ghost: 0x8000, // For a tile's COLLISION, use one of these bit combinations monster_typical: 0x6f00, // everything but ghost and rover monster_any: 0xff00, // everything including ghost (only used for monster/fire compat flag) // Combo masks used for matching all_but_ghost: 0xffff & ~0x8000, all_but_real_player: 0xffff & ~0x0003, all: 0xffff, }; // Item pickup priority, which both actors and items have. An actor will pick up an item if the // item's priority is greater than or equal to the actor's. export const PICKUP_PRIORITIES = { never: 4, // cc2 blocks, never pick anything up always: 3, // all actors; blue keys, yellow teleporters (everything picks up except cc2 blocks) // TODO is this even necessary? in cc2 the general rule seems to be that anything stepping on // an item picks it up, and collision is used to avoid that most of the time normal: 3, // actors with inventories; most items player: 1, // players and doppelgangers; red keys (ignored by everything else) real_player: 0, }; export const COMPAT_RULESET_LABELS = { lexy: "Lexy", steam: "Steam", 'steam-strict': "Steam (strict)", lynx: "Lynx", ms: "Microsoft", custom: "Custom", }; export const COMPAT_RULESET_ORDER = ['lexy', 'steam', 'steam-strict', 'lynx', 'ms', 'custom']; // FIXME some of the names of the flags themselves kinda suck export const COMPAT_FLAGS = [ // Level loading // TODO? /strictly/ speaking, these should be turned on for lynx+ms/lynx respectively, but then i'd // have to also alter the behavior of the corresponding terrain, which seems kind of silly { key: 'no_auto_convert_ccl_popwalls', label: "Recessed walls under actors in CCL levels are left alone", rulesets: new Set(['steam-strict']), }, { key: 'no_auto_convert_ccl_blue_walls', label: "Blue walls under blocks in CCL levels are left alone", rulesets: new Set(['steam-strict']), }, // Core { key: 'allow_double_cooldowns', label: "Actors may cooldown twice in one tic", rulesets: new Set(['steam', 'steam-strict', 'lynx']), }, { key: 'no_separate_idle_phase', label: "Actors teleport immediately after moving", rulesets: new Set(['steam', 'steam-strict']), }, { key: 'player_moves_last', label: "Players always move last", rulesets: new Set(['lynx', 'ms']), }, { key: 'player_protected_by_items', label: "Players can't be trampled when standing on items", rulesets: new Set(['lynx']), }, { // Note that this requires no_early_push as well key: 'player_safe_at_decision_time', label: "Players can't be trampled at decision time", rulesets: new Set(['lynx']), }, { key: 'emulate_60fps', label: "Game runs at 60 FPS", rulesets: new Set(['steam', 'steam-strict']), }, { key: 'reuse_actor_slots', label: "Game reuses slots in the actor list", rulesets: new Set(['lynx']), }, { key: 'force_lynx_animation_lengths', label: "Animations use Lynx duration", rulesets: new Set(['lynx']), }, // Tiles { // XXX this is goofy key: 'tiles_react_instantly', label: "Tiles react when approached", rulesets: new Set(['ms']), }, { key: 'rff_actually_random', label: "Random force floors are actually random", rulesets: new Set(['ms']), }, { key: 'no_backwards_override', label: "Players can't override backwards on a force floor", rulesets: new Set(['lynx']), }, { key: 'traps_like_lynx', label: "Traps eject faster, and even when already open", rulesets: new Set(['lynx']), }, { key: 'blue_floors_vanish_on_arrive', label: "Fake blue walls vanish on arrival", rulesets: new Set(['lynx']), }, { key: 'green_teleports_can_fail', label: "Green teleporters sometimes fail", rulesets: new Set(['steam-strict']), }, // Items { key: 'bombs_detonate_on_arrive', label: "Mines detonate only when stepped on", rulesets: new Set(['lynx', 'ms']), }, { key: 'bombs_immediately_detonate_under_players', label: "Mines under players detonate at level start", rulesets: new Set(['steam-strict']), }, { key: 'cloned_bowling_balls_can_be_lost', label: "Bowling balls on cloners are destroyed when fired at point blank", rulesets: new Set(['steam-strict']), }, { key: 'monsters_ignore_keys', label: "Monsters completely ignore keys", rulesets: new Set(['ms']), }, // Blocks { key: 'no_early_push', label: "Pushing blocks happens at move time (block slapping is disabled)", // XXX wait but the DEFAULT behavior allows block slapping, which lynx has, so why is lynx listed here? rulesets: new Set(['lynx', 'ms']), }, { key: 'use_legacy_hooking', label: "Pulling blocks with the hook happens at decision time", rulesets: new Set(['steam', 'steam-strict']), }, { key: 'no_directly_pushing_sliding_blocks', label: "Don't directly push sliding blocks", rulesets: new Set(['steam', 'steam-strict']), }, { key: 'use_pgchip_ice_blocks', label: "Ice blocks emulate pgchip rules", rulesets: new Set(['ms']), }, { key: 'allow_pushing_blocks_off_faux_walls', label: "Blocks may be pushed off of blue (fake), invisible, and revealing walls", rulesets: new Set(['lynx']), }, { key: 'emulate_spring_mining', label: "Spring mining is possible", rulesets: new Set(['steam-strict']), /* XXX not implemented }, { key: 'emulate_flicking', label: "Flicking is possible", rulesets: new Set(['ms']), */ }, // Monsters { // TODO? in lynx they ignore the button while in motion too // TODO what about in a trap, in every game?? // TODO what does ms do when a tank is on ice or a ff? wiki's description is wacky // TODO yellow tanks seem to have memory too?? key: 'tanks_always_obey_button', label: "Blue tanks on cloners obey blue buttons", rulesets: new Set(['steam-strict']), }, { key: 'tanks_ignore_button_while_moving', label: "Blue tanks ignore blue buttons while moving", rulesets: new Set(['lynx']), }, { key: 'blobs_use_tw_prng', label: "Blobs use the Tile World RNG", rulesets: new Set(['lynx']), }, { key: 'teeth_target_internal_position', label: "Teeth target the player's internal position", rulesets: new Set(['lynx']), }, { key: 'rff_blocks_monsters', label: "Random force floors block monsters", rulesets: new Set(['ms']), }, { key: 'bonking_isnt_instant', label: "Bonking while sliding doesn't apply instantly", rulesets: new Set(['lynx', 'ms']), }, { key: 'fire_allows_most_monsters', label: "Fire doesn't block monsters, except bugs and walkers", rulesets: new Set(['ms']), }, ]; export function compat_flags_for_ruleset(ruleset) { let compat = {}; for (let compatdef of COMPAT_FLAGS) { if (compatdef.rulesets.has(ruleset)) { compat[compatdef.key] = true; } } return compat; }