'use strict' class Input {} setEnumValues(Input, ['left', 'right', 'center', 'forward', 'back', 'action']) class Chapter {} setEnumValues(Chapter, ['act1', 'act2', 'act3', 'act4', 'act5', 'credit']) class Zone {} setEnumValues(Zone, [ 'antechannel', 'capsule', 'circular', 'entente', 'forest', 'metamondst', 'nataniev', 'rainre', 'stones', 'studio' ]) const SealOrder = Object.freeze([ Zone.forest, Zone.stones, Zone.rainre, Zone.metamondst, Zone.antechannel ]) class MazeAxis {} setEnumValues(MazeAxis, ['x', 'y']) class MazeEffect {} setEnumValues(MazeEffect, ['incr', 'decr', 'exit']) const ententeIcons = (function () { const ententeIcons = new Map() ententeIcons.set(-1, 'left') ententeIcons.set(0, 'straight') ententeIcons.set(1, 'right') return Object.freeze(ententeIcons) })() const recordsByChapter = (function () { const recordsByChapter = new Map() recordsByChapter.set(Chapter.act1, 'music_act1') recordsByChapter.set(Chapter.act2, 'music_act2') recordsByChapter.set(Chapter.act3, 'music_act3') recordsByChapter.set(Chapter.act4, 'music_act4') recordsByChapter.set(Chapter.act5, 'music_act5') recordsByChapter.set(Chapter.credit, 'music_credit') return Object.freeze(recordsByChapter) })() const ambienceByZone = (function () { const ambienceByZone = new Map() ambienceByZone.set(Zone.antechannel, 'ambient_antechannel') ambienceByZone.set(Zone.capsule, 'ambient_capsule') ambienceByZone.set(Zone.circular, 'ambient_circular') ambienceByZone.set(Zone.entente, null) // Weird omission, eh? ambienceByZone.set(Zone.forest, 'ambient_forest') ambienceByZone.set(Zone.metamondst, 'ambient_metamondst') ambienceByZone.set(Zone.nataniev, 'ambient_nataniev') ambienceByZone.set(Zone.rainre, 'ambient_rainre') ambienceByZone.set(Zone.stones, 'ambient_stones') ambienceByZone.set(Zone.studio, 'ambient_studio') return Object.freeze(ambienceByZone) })() class SubjectType {} setEnumValues(SubjectType, ['node', 'puzzle', 'none']) class PuzzleType {} setEnumValues(PuzzleType, ['door', 'terminal']) class Node { constructor (id, zone, subjects) { this.id = id this.zone = zone this.subjects = subjects Object.freeze(this) } } const nodesByID = (function () { const nodesByID = new Map() const addNode = (id, zone, subject0, subject1, subject2, subject3) => { nodesByID.set( id, Object.freeze( new Node(id, zone, [subject0, subject1, subject2, subject3]) ) ) } const node = (nodeID, orientation, mapIgnore = false) => { return Object.freeze({ type: SubjectType.node, nodeID, orientation, mapIgnore }) } const terminal = puzzleID => { return Object.freeze({ type: SubjectType.puzzle, puzzleType: PuzzleType.terminal, puzzleID }) } const door = ( puzzleID, nodeID, orientation, secretNodeID, secretOrientation ) => { return Object.freeze({ type: SubjectType.puzzle, puzzleType: PuzzleType.door, puzzleID, nodeID, orientation, alternateNodeID: secretNodeID, alternateOrientation: secretOrientation }) } const secretDoor = (puzzleID, secretNodeID, secretOrientation) => { return Object.freeze({ type: SubjectType.puzzle, puzzleType: PuzzleType.door, puzzleID, alternateNodeID: secretNodeID, alternateOrientation: secretOrientation }) } const maze = ( subject, puzzleID, axis, effect, amount, mazeNodeID, mazeOrientation ) => { return Object.freeze({ nodeID: subject.nodeID, orientation: subject.orientation, type: SubjectType.puzzle, puzzleType: PuzzleType.door, puzzleID, axis, effect, amount, alternateNodeID: mazeNodeID, alternateOrientation: mazeOrientation }) } const none = Object.freeze({ type: SubjectType.none }) addNode("fa", Zone.forest, node("fb"), none, door(28, "ea"), none) addNode("fb", Zone.forest, node("fc"), none, node("fa"), none) addNode("fc", Zone.forest, node("fk"), node("fj", 2), node("fb"), node("fd", 0)) addNode("fd", Zone.forest, node("fe"), none, node("fc", 1), none) addNode("fe", Zone.forest, node("ff"), none, node("fd"), terminal(4)) addNode("ff", Zone.forest, node("fg", 1), none, node("fe"), none) addNode("fg", Zone.forest, node("fl"), node("fh", 0), terminal(1), node("ff", 2)) addNode("fh", Zone.forest, node("fi", 2), none, node("fg", 3), none) addNode("fi", Zone.forest, node("fh", 2), none, node("fj", 0), none) addNode("fj", Zone.forest, node("fc", 3), none, node("fi", 0), none) addNode("fk", Zone.forest, door(25, "ao", 2), none, node("fc"), none) addNode("fl", Zone.forest, door(3, "sa"), none, node("fg"), node("fm", 0)) addNode("sa", Zone.studio, node("sc"), node("sb"), door(3, "fl"), terminal(2)) addNode("fm", Zone.forest, node("fn"), none, node("fl", 1), none) addNode("sb", Zone.studio, node("sd"), none, none, node("sa")) addNode("sd", Zone.studio, node("sg"), door(7, "ci"), node("sb"), node("sc")) addNode("sc", Zone.studio, node("sf"), node("sd"), node("sa"), none) addNode("fn", Zone.forest, terminal(31), terminal(2), node("fm"), none) addNode("se", Zone.studio, none, node("sf"), terminal(5), none) addNode("sf", Zone.studio, secretDoor(6, "ez", 1), node("sg"), node("sc"), node("se")) addNode("sg", Zone.studio, terminal(35), none, node("sd"), node("sf")) addNode("ci", Zone.circular, none, node("ca"), none, node("sd")) addNode("ca", Zone.circular, node("cb"), terminal(16), node("ch"), door(7, "ci")) addNode("ch", Zone.circular, node("ca"), none, node("cg"), none) addNode("cg", Zone.circular, node("ch"), none, node("cf"), door(8, "cl", 2)) addNode("cf", Zone.circular, node("cg"), none, node("ce"), none) addNode("ce", Zone.circular, node("cf"), none, node("cd"), door(9, "ck", 1)) addNode("cd", Zone.circular, node("ce"), none, node("cc"), none) addNode("cc", Zone.circular, node("cd"), none, node("cb"), node("cj", 0)) addNode("cb", Zone.circular, node("cc"), none, node("ca"), none) addNode("cl", Zone.circular, node("cg", 1), none, node("la"), none) addNode("ck", Zone.circular, none, node("aa"), none, node("ce", 1)) addNode("cj", Zone.circular, terminal(14), none, node("cc", 1), none) addNode("ey", Zone.entente, terminal(37), none, none, node("le")) addNode("la", Zone.stones, door(8, "cl", 0), node("lb"), node("lc"), none) addNode("lc", Zone.stones, node("la"), node("ld"), none, none) addNode("ld", Zone.stones, node("lb"), node("le"), none, node("lc")) addNode("lb", Zone.stones, terminal(12), none, node("ld"), node("la")) const node39Door = door(11, "ra", null, "ey") addNode("le", Zone.stones, terminal(10), node39Door, node("lf"), node("ld")) addNode("lf", Zone.stones, node("le"), none, node("lg"), none) addNode("lg", Zone.stones, node("lf"), none, none, node("lh")) addNode("lh", Zone.stones, none, node("lg"), node("lj"), node("li")) addNode("li", Zone.stones, none, node("lh"), none, terminal(35)) addNode("lj", Zone.stones, node("lh"), none, node("lk"), none) addNode("ra", Zone.rainre, terminal(13), none, terminal(1), node("le")) addNode("lk", Zone.stones, node("lj"), none, door(15, "ma", 2), none) addNode("an", Zone.antechannel, none, node("ao"), node("am"), none) addNode("ao", Zone.antechannel, door(25, "fk", 2), none, node("ap"), node("an")) addNode("ap", Zone.antechannel, node("ao"), none, terminal(21), node("am")) addNode("ne", Zone.nataniev, door(40, "nd"), none, terminal(41), none) addNode("aa", Zone.antechannel, node("ad"), node("ab"), none, door(9, "ck", 3)) addNode("ab", Zone.antechannel, node("ae"), node("ac"), none, node("aa")) addNode("ad", Zone.antechannel, node("ag"), node("ae"), node("aa"), none) addNode("ae", Zone.antechannel, terminal(39), node("af"), node("ab"), node("ad")) addNode("af", Zone.antechannel, node("aj"), none, node("ac"), node("ae")) addNode("ag", Zone.antechannel, node("ah"), none, node("ad"), none) addNode("aj", Zone.antechannel, node("ak"), none, node("af"), none) addNode("ah", Zone.antechannel, node("ai"), none, node("ag"), none) addNode("ak", Zone.antechannel, node("al"), none, node("af", null, true), none) // Feature, not a bug! addNode("ai", Zone.antechannel, none, none, node("ah"), door(19, "mk")) addNode("al", Zone.antechannel, door(26, "am"), none, node("ak"), none) addNode("mi", Zone.metamondst, node("ml"), node("mj"), none, node("me")) addNode("nf", Zone.nataniev, node("mi"), none, none, none) // TODO: something interesting const node65Maze = maze(node("ee"), 42, MazeAxis.y, MazeEffect.exit, 0, "eq", 3) addNode("ep", Zone.entente, node("ej"), none, node65Maze, none) addNode("me", Zone.metamondst, node("md"), node("mi"), none, node("mf")) addNode("mj", Zone.metamondst, terminal(18), door(19, "mk"), none, node("mi")) addNode("mf", Zone.metamondst, node("mg"), node("me"), none, none) addNode("mk", Zone.metamondst, none, node("ai"), none, node("mj")) addNode("ml", Zone.metamondst, node("mm"), none, node("mi"), none) addNode("md", Zone.metamondst, node("mc"), none, node("me"), none) addNode("mg", Zone.metamondst, node("mh"), none, node("mf"), none) addNode("mh", Zone.metamondst, none, none, node("mg"), door(30, "pa")) addNode("am", Zone.antechannel, node("an"), node("ap"), door(26, "al"), terminal(27)) addNode("mp", Zone.metamondst, node("mo"), none, none, node("mm")) addNode("pb", Zone.capsule, none, door(33, "na"), node("pa"), none) addNode("mc", Zone.metamondst, node("ma"), none, node("md"), none) addNode("mm", Zone.metamondst, node("mn"), node("mp"), node("ml"), none) addNode("mn", Zone.metamondst, none, node("mo"), node("mm"), terminal(20)) addNode("mo", Zone.metamondst, none, none, node("mp"), node("mn")) addNode("ac", Zone.antechannel, node("af"), none, terminal(47), node("ab")) addNode("ma", Zone.metamondst, door(15, "lk", 0), none, node("mc"), node("mb")) addNode("mb", Zone.metamondst, none, node("ma"), none, terminal(1)) addNode("pa", Zone.capsule, node("pb"), door(30, "mh"), none, node("pc")) addNode("pc", Zone.capsule, none, node("pa"), none, node("pd")) const node89Maze = maze(node("eo"), 42, MazeAxis.x, MazeEffect.decr, 1) addNode("ea", Zone.entente, node89Maze, none, node("eb"), none) addNode("eb", Zone.entente, node("ea"), none, node("ec"), none) const node91Maze = maze(node("eo"), 42, MazeAxis.x, MazeEffect.incr, 3, "ed") addNode("ec", Zone.entente, node("eb"), terminal(23), node91Maze, none) addNode("ed", Zone.entente, node("ec"), none, node("ee"), none) addNode("ee", Zone.entente, node("ed"), none, node("ef"), none) addNode("ef", Zone.entente, node("ee"), none, node("eg"), none) addNode("eg", Zone.entente, node("ef"), terminal(24), node("eh"), none) addNode("eh", Zone.entente, node("eg"), none, node("ei"), none) addNode("ei", Zone.entente, node("eh"), none, node("ej"), none) addNode("ej", Zone.entente, node("ei"), node("ek"), node("ep"), node("em")) addNode("ek", Zone.entente, none, node("el"), none, node("ej")) const node100Maze = maze(node("eg", 2), 42, MazeAxis.y, MazeEffect.decr, 1) addNode("el", Zone.entente, none, node100Maze, none, node("ek")) addNode("em", Zone.entente, none, node("ej"), terminal(38), node("en")) const node102Maze = maze(node("eg", 2), 42, MazeAxis.y, MazeEffect.incr, 4) addNode("en", Zone.entente, none, node("em"), none, node102Maze) addNode("eo", Zone.entente, node("ec"), none, node("ea"), none) addNode("ex", Zone.entente, none, node("eq"), none, node("ew")) addNode("ew", Zone.entente, none, node("ex"), none, node("ev")) addNode("ev", Zone.entente, none, node("ew"), none, node("eq")) addNode("eq", Zone.entente, none, node("ev"), none, node("er")) addNode("er", Zone.entente, none, node("eq"), none, node("es")) addNode("es", Zone.entente, none, node("er"), terminal(43), node("et")) addNode("et", Zone.entente, none, node("es"), none, node("eu")) addNode("eu", Zone.entente, none, node("et"), none, node("fa", 0)) addNode("na", Zone.nataniev, node("nb"), node("nd"), none, door(33, "pb")) addNode("nd", Zone.nataniev, node("nc"), terminal(36), door(40, "ne"), node("na")) addNode("nc", Zone.nataniev, none, none, node("nd"), node("nb")) addNode("nb", Zone.nataniev, none, node("nc"), node("na"), none) addNode("ez", Zone.entente, none, none, none, node("sf", 2)) addNode("pd", Zone.capsule, none, node("pc"), none, node("pe")) // was 141 addNode("pe", Zone.capsule, none, node("pd"), none, door(54, "pf", 3)) // was 142 addNode("pf", Zone.capsule, none, door(54, "pe", 1), none, none) // was 143 return Object.freeze(nodesByID) })() const puzzlesByID = (function () { const puzzlesByID = new Map() const addPuzzle = puzzle => { puzzlesByID.set(puzzle.id, Object.freeze(puzzle)) } const lock = (chapter, seals) => { return Object.freeze({ chapter, seals }) } const ententeMazeGoal = Object.freeze( new Map([[MazeAxis.x, 2], [MazeAxis.y, 17]]) ) const act2Lock = lock(Chapter.act2, [Zone.forest, Zone.rainre]) const act3Lock = lock(Chapter.act3, [Zone.metamondst, Zone.rainre]) const act4Lock = lock(Chapter.act4, [Zone.rainre, Zone.antechannel]) addPuzzle(new ClockTerminal(1)) addPuzzle(new EnergyTerminal(2)) addPuzzle(new EnergyDoor(3, [2])) addPuzzle(new SealTerminal(4, Zone.forest)) addPuzzle(new StudioTerminal(5, [Zone.stones, Zone.antechannel])) addPuzzle(new SecretDoor(6)) addPuzzle(new ClockDoor(7, 0)) addPuzzle(new ClockDoor(8, 2)) addPuzzle(new ClockDoor(9, 1)) addPuzzle(new EnergyTerminal(10)) addPuzzle(new SecretEnergyDoor(11, [10], [31])) addPuzzle(new SealTerminal(12, Zone.stones)) addPuzzle(new SealTerminal(13, Zone.rainre)) addPuzzle(new KillTerminal(14, 50)) addPuzzle(new SealDoor(15, [act2Lock, act4Lock], 1)) addPuzzle(new ProgressTerminal(16)) // We removed puzzle 17 (the illusion puzzle) addPuzzle(new EnergyTerminal(18)) addPuzzle(new EnergyDoor(19, [18])) addPuzzle(new SealTerminal(20, Zone.metamondst)) addPuzzle(new SealTerminal(21, Zone.antechannel)) // unused addPuzzle(new EntenteTerminal(23, MazeAxis.x, ententeMazeGoal)) addPuzzle(new EntenteTerminal(24, MazeAxis.y, ententeMazeGoal)) addPuzzle(new SealDoor(25, [act3Lock], 1)) addPuzzle(new EnergyDoor(26, [27])) addPuzzle(new EnergyTerminal(27)) addPuzzle(new StudioDoor(28, 1)) // unused addPuzzle(new StudioDoor(30)) addPuzzle(new EnergyTerminal(31, 1)) // unused addPuzzle(new EnergyDoor(33, [47])) addPuzzle(new AudioTerminal(34, 1)) addPuzzle(new AudioTerminal(35, 1)) addPuzzle(new EnergyTerminal(36)) addPuzzle(new SecretTerminal(37)) addPuzzle(new EnergyTerminal(38, 1)) // Entente Fuse addPuzzle(new EnergyTerminal(39, 1)) addPuzzle(new EndgameDoor(40, [36, 47])) addPuzzle(new EndgameCredit(41)) addPuzzle(new EntenteDoor(42, ententeMazeGoal)) addPuzzle(new EntenteProgressTerminal(43)) // unused // unused // unused addPuzzle(new EnergyTerminal(47)) // Spare Fuse // unused // unused // unused // unused // unused // unused addPuzzle(new TimeDoor(54, 15, 7)) return Object.freeze(puzzlesByID) })() const createDefaultState = function () { const state = { seals: new Set(), fuses: new Set(), clock: 0, audio: true, studio: false, secret: false, timeDoor: false } for (const puzzle of puzzlesByID.values()) { if (puzzle instanceof EnergyTerminal && puzzle.filledOnNewGame) { state.fuses.add(puzzle.id) } } return state }