]> git.localhorst.tv Git - alttp.git/blob - resources/js/helpers/logic.js
59757e2415dd7f3a426b67e45afd22cb21fb46f6
[alttp.git] / resources / js / helpers / logic.js
1 import {
2         getDungeonBoss,
3         getDungeonPrize,
4         getGTBoss,
5         getGTCrystals,
6         hasDungeonBoss,
7         hasDungeonPrize,
8 } from './tracker';
9
10 const and = (...predicates) => (...args) =>
11         predicates.reduce((acc, cur) => acc && cur(...args), true);
12
13 const or = (...predicates) => (...args) =>
14         predicates.reduce((acc, cur) => acc || cur(...args), false);
15
16 const when = (condition, then, otherwise) => (...args) =>
17         condition(...args) ? then(...args) : otherwise(...args);
18
19 const alwaysAvailable = () => true;
20
21 const neverAvailable = () => false;
22
23 const fromBool = b => (...args) => b(...args) ? 'available' : 'unavailable';
24
25 const isInverted = (config) => config.worldState === 'inverted';
26
27 const agaDead = (config, dungeons, state) =>
28         hasDungeonBoss(state, dungeons.find(d => d.id === 'ct'));
29
30 const countCrystals = (config, dungeons, state) => dungeons
31         .filter(dungeon =>
32                 (getDungeonPrize(state, dungeon) || 'crystal').endsWith('crystal') &&
33                 dungeon.prize &&
34                 hasDungeonPrize(state, dungeon)
35         ).length;
36
37 const countRedCrystals = (config, dungeons, state) => dungeons
38         .filter(dungeon =>
39                 getDungeonPrize(state, dungeon) === 'red-crystal' &&
40                 hasDungeonPrize(state, dungeon)
41         ).length;
42
43 const hasRedCrystals = n => (...args) => countRedCrystals(...args) >= n;
44
45 const hasGTCrystals = (config, dungeons, state) =>
46         countCrystals(config, dungeons, state) >= getGTCrystals(config);
47
48 const countPendants = (config, dungeons, state) => dungeons
49         .filter(dungeon =>
50                 (getDungeonPrize(state, dungeon) || '').endsWith('pendant') &&
51                 hasDungeonPrize(state, dungeon)
52         ).length;
53
54 const hasGreenPendant = (config, dungeons, state) => dungeons
55         .filter(dungeon =>
56                 getDungeonPrize(state, dungeon) === 'green-pendant' &&
57                 hasDungeonPrize(state, dungeon)
58         ).length >= 1;
59
60 const hasPendants = n => (...args) => countPendants(...args) >= n;
61
62 // Equipment
63
64 const countBottles = (config, dungeons, state) =>
65         ['bottle-1', 'bottle-2', 'bottle-3', 'bottle-4'].filter(b => !!state[b]).length;
66
67 const hasBig = dungeon => (config, dungeons, state) =>
68         !config.wildBig || !!state[`${dungeon}-big-key`];
69
70 const hasBird = (config, dungeons, state) => !!state['bird'];
71
72 const hasBombos = (config, dungeons, state) => !!state['bombos'];
73
74 const hasBook = (config, dungeons, state) => !!state['book'];
75
76 const hasBoom = (config, dungeons, state) => !!(state['blue-boomerang'] || state['red-boomerang']);
77
78 const hasBoots = (config, dungeons, state) => !!state['boots'];
79
80 const hasBottle = n => (...args) => countBottles(...args) >= (n || 1);
81
82 const hasBow = (config, dungeons, state) => !!state['bow'];
83
84 const hasBugnet = (config, dungeons, state) => !!state['bugnet'];
85
86 const hasByrna = (config, dungeons, state) => !!state['byrna'];
87
88 const hasCape = (config, dungeons, state) => !!state['cape'];
89
90 const hasFireRod = (config, dungeons, state) => !!state['fire-rod'];
91
92 const hasFlute = (config, dungeons, state) => !!state['flute'];
93
94 const hasHammer = (config, dungeons, state) => !!state['hammer'];
95
96 const hasHookshot = (config, dungeons, state) => !!state['hookshot'];
97
98 const hasIceRod = (config, dungeons, state) => !!state['ice-rod'];
99
100 const hasLamp = (config, dungeons, state) => !!state['lamp'];
101
102 const hasMagicBars = n => (config, dungeons, state) => {
103         let bars = 1 + (state['bottle'] || 0);
104         if (state['half-magic']) {
105                 bars *= 2;
106         }
107         if (state['quarter-magic']) {
108                 bars *= 2;
109         }
110         return bars >= (n || 1);
111 };
112
113 const hasMirror = (config, dungeons, state) => !!state['mirror'];
114
115 const hasMMMedallion = (config, dungeons, state) =>
116         !!state['mm-medallion'] && !!state[state['mm-medallion']];
117
118 const hasMoonpearl = (config, dungeons, state) => !!state['moonpearl'];
119
120 const hasMushroom = (config, dungeons, state) => !!state['mushroom'];
121
122 const hasPowder = (config, dungeons, state) => !!state['powder'];
123
124 const hasQuake = (config, dungeons, state) => !!state['quake'];
125
126 const hasShovel = (config, dungeons, state) => !!state['shovel'];
127
128 const hasSmall = (dungeon, amount) => (config, dungeons, state) =>
129         !config.wildSmall || state[`${dungeon}-small-key`] >= (amount || 1);
130
131 const hasSomaria = (config, dungeons, state) => !!state['somaria'];
132
133 const hasSword = n => (config, dungeons, state) => state['sword'] >= (n || 1);
134
135 const hasTRMedallion = (config, dungeons, state) =>
136         !!state['tr-medallion'] && !!state[state['tr-medallion']];
137
138 // Abilities
139
140 const canBomb = () => true;
141
142 const canBonk = hasBoots;
143
144 const canDarkRoom = hasLamp;
145
146 const canShootArrows = hasBow;
147
148 const canFlipSwitches = or(
149         canBomb,
150         hasBoom,
151         canShootArrows,
152         hasSword(),
153         hasHammer,
154         hasHookshot,
155         hasSomaria,
156         hasByrna,
157         hasFireRod,
158         hasIceRod,
159 );
160
161 const canGetGoodBee = and(hasBugnet, hasBottle(), or(canBonk, and(hasSword(), hasQuake)));
162
163 const canLift = (config, dungeons, state) => state['lift'] >= 1;
164
165 const canHeavyLift = (config, dungeons, state) => state['lift'] >= 2;
166
167 const canActivateFlute = when(isInverted,
168         and(
169                 hasMoonpearl,
170                 or(
171                         // partial copy of east light world
172                         agaDead,
173                         and(hasMoonpearl, or(and(canLift, hasHammer), canHeavyLift)),
174                 ),
175         ),
176         alwaysAvailable,
177 );
178
179 const canFly = or(hasBird, and(hasFlute, canActivateFlute));
180
181 const canKill = damage => damage && damage < 6
182         ? or(hasBow, hasFireRod, hasHammer, hasSomaria, hasSword(1), canBomb, hasByrna)
183         : or(hasBow, hasFireRod, hasHammer, hasSomaria, hasSword(1));
184
185 const canMedallion = hasSword();
186
187 const canMeltThings = or(hasFireRod, and(hasBombos, canMedallion));
188
189 const canPassCurtains = hasSword();
190
191 const canSwim = (config, dungeons, state) => !!state['flippers'];
192
193 const canTablet = and(hasBook, hasSword(2));
194
195 const canTorch = or(hasFireRod, hasLamp);
196
197 const canTorchDarkRoom = or(canDarkRoom, canTorch);
198
199 // Regions
200
201 const westDeathMountain = or(
202         canFly,
203         and(canLift, canDarkRoom),
204 );
205
206 const westDarkDeathMountain = when(isInverted,
207         or(canFly, and(canLift, canDarkRoom)),
208         westDeathMountain,
209 );
210
211 const eastDeathMountain = when(isInverted,
212         or(
213                 and(canHeavyLift, or(
214                         // copy of eastDarkDeathMountain, to avoid circular reference
215                         westDarkDeathMountain,
216                         and(westDeathMountain, hasMoonpearl, hasHookshot, hasMirror),
217                 )),
218                 and(westDeathMountain, hasMoonpearl, hasHookshot),
219         ),
220         and(
221                 westDeathMountain,
222                 or(
223                         hasHookshot,
224                         and(hasHammer, hasMirror),
225                 ),
226         ),
227 );
228
229 const northDeathMountain = when(isInverted,
230         and(eastDeathMountain, hasMoonpearl, hasHammer),
231         and(
232                 westDeathMountain,
233                 or(
234                         hasMirror,
235                         and(hasHammer, hasHookshot),
236                 ),
237         ),
238 );
239
240 const eastDarkDeathMountain = when(isInverted,
241         or(
242                 westDarkDeathMountain,
243                 and(westDeathMountain, hasMoonpearl, hasHookshot, hasMirror),
244         ),
245         and(
246                 eastDeathMountain,
247                 canHeavyLift,
248         ),
249 );
250
251 const eastLightWorld = when(isInverted,
252         or(
253                 agaDead,
254                 and(hasMoonpearl, or(and(canLift, hasHammer), canHeavyLift)),
255                 and(canFly, canHeavyLift),
256         ),
257         alwaysAvailable,
258 );
259
260 const westLightWorld = eastLightWorld;
261
262 const southLightWorld = eastLightWorld;
263
264 const eastDarkWorld = when(isInverted,
265         or(canFly, canSwim, hasHammer, and(hasMoonpearl, eastLightWorld)),
266         and(
267                 hasMoonpearl,
268                 or(
269                         agaDead,
270                         canHeavyLift,
271                         and(canLift, hasHammer),
272                 ),
273         ),
274 );
275
276 const westDarkWorld = when(isInverted,
277         alwaysAvailable,
278         and(
279                 hasMoonpearl,
280                 or(
281                         and(canLift, hasHammer),
282                         canHeavyLift,
283                         and(eastDarkWorld, hasHookshot, or(canSwim, canLift, hasHammer)),
284                 ),
285         ),
286 );
287
288 const southDarkWorld = when(isInverted,
289         alwaysAvailable,
290         or(
291                 westDarkWorld,
292                 and(eastDarkWorld, hasMoonpearl, hasHammer),
293         ),
294 );
295
296 const mireArea = when(isInverted,
297         or(canFly, and(hasMirror, southLightWorld)),
298         and(canFly, canHeavyLift),
299 );
300
301 // Bosses
302
303 const BOSS_RULES = {
304         aga: or(hasBugnet, hasHammer, hasSword()),
305         armos: or(
306                 hasSword(), hasHammer, canShootArrows, hasBoom,
307                 and(hasMagicBars(4), or(hasFireRod, hasIceRod)),
308                 and(hasMagicBars(2), or(hasByrna, hasSomaria)),
309         ),
310         arrghus: and(hasHookshot, or(
311                 hasSword(),
312                 hasHammer,
313                 and(or(hasMagicBars(2), canShootArrows), or(hasFireRod, hasIceRod)),
314         )),
315         blind: or(hasSword(), hasHammer, hasSomaria, hasByrna),
316         helma: and(or(canBomb, hasHammer), or(hasSword(), canShootArrows)),
317         kholdstare: and(canMeltThings, or(
318                 hasHammer,
319                 hasSword(),
320                 and(hasFireRod, hasMagicBars(3)),
321                 and(hasFireRod, hasBombos, hasMagicBars(2), canMedallion),
322         )),
323         lanmolas: or(
324                 hasSword(), hasHammer, canShootArrows, hasFireRod, hasIceRod, hasByrna, hasSomaria,
325         ),
326         moldorm: or(hasSword(), hasHammer),
327         mothula: or(
328                 hasSword(),
329                 hasHammer,
330                 and(hasMagicBars(2), or(hasFireRod, hasSomaria, hasByrna)),
331                 canGetGoodBee,
332         ),
333         trinexx: and(hasFireRod, hasIceRod, or(
334                 hasSword(3),
335                 hasHammer,
336                 and(hasMagicBars(2), hasSword(2)),
337                 and(hasMagicBars(4), hasSword(1)),
338         )),
339         vitreous: or(hasSword(), hasHammer, canShootArrows),
340 };
341
342 const canKillBoss = dungeon => (config, dungeons, state) => {
343         const boss = getDungeonBoss(state, dungeons.find(d => d.id === dungeon));
344         return BOSS_RULES[boss](config, dungeons, state);
345 };
346
347 const canKillGTBoss = which => (config, dungeons, state) => {
348         const boss = getGTBoss(state, which);
349         return BOSS_RULES[boss](config, dungeons, state);
350 };
351
352 // Dungeons
353
354 const canEnterHC = when(isInverted,
355         and(hasMoonpearl, eastLightWorld),
356         alwaysAvailable,
357 );
358
359 const canEnterCT = when(isInverted,
360         westDarkDeathMountain,
361         or(hasCape, hasSword(2)),
362 );
363
364 const canEnterGT = when(isInverted,
365         and(eastLightWorld, hasGTCrystals, hasMoonpearl),
366         and(eastDarkDeathMountain, hasGTCrystals, hasMoonpearl),
367 );
368
369 const canEnterEP = when(isInverted,
370         and(hasMoonpearl, eastLightWorld),
371         alwaysAvailable,
372 );
373
374 const canEnterDPFront = when(isInverted,
375         and(southLightWorld, hasBook),
376         or(hasBook, and(mireArea, hasMirror)),
377 );
378 const canEnterDPBack = when(isInverted,
379         and(canEnterDPFront, canLift),
380         or(and(canEnterDPFront, canLift), and(mireArea, hasMirror)),
381 );
382
383 const canEnterTH = northDeathMountain;
384
385 const canEnterPD = when(isInverted,
386         eastDarkWorld,
387         and(eastDarkWorld, hasMoonpearl),
388 );
389
390 const canEnterSP = when(isInverted,
391         and(southLightWorld, hasMirror, hasMoonpearl, canSwim),
392         and(southDarkWorld, hasMirror, hasMoonpearl, canSwim),
393 );
394
395 const canEnterSWFront = when(isInverted,
396         westDarkWorld,
397         and(westDarkWorld, hasMoonpearl),
398 );
399 const canEnterSWMid = canEnterSWFront;
400 const canEnterSWBack = and(canEnterSWMid, hasFireRod);
401
402 const canEnterTT = when(isInverted,
403         westDarkWorld,
404         and(westDarkWorld, hasMoonpearl),
405 );
406
407 const canEnterIP = when(isInverted,
408         and(canSwim, canMeltThings),
409         and(canSwim, canHeavyLift, hasMoonpearl, canMeltThings),
410 );
411 const rightSideIP = or(hasHookshot, hasSmall('ip'));
412
413 const canEnterMM = when(isInverted,
414         and(
415                 mireArea,
416                 hasMMMedallion,
417                 canMedallion,
418                 or(canBonk, hasHookshot),
419                 canKill(8),
420         ),
421         and(
422                 mireArea,
423                 hasMoonpearl,
424                 hasMMMedallion,
425                 canMedallion,
426                 or(canBonk, hasHookshot),
427                 canKill(8),
428         ),
429 );
430
431 const canEnterTRFront = when(isInverted,
432         and(
433                 eastDarkDeathMountain,
434                 canMedallion,
435                 hasTRMedallion,
436                 hasSomaria,
437         ),
438         and(
439                 eastDeathMountain,
440                 canHeavyLift,
441                 hasHammer,
442                 hasMoonpearl,
443                 canMedallion,
444                 hasTRMedallion,
445                 hasSomaria,
446         ),
447 );
448 const canEnterTRWest = when(isInverted,
449         and(eastDeathMountain, hasMirror),
450         neverAvailable,
451 );
452 const canEnterTREast = and(canEnterTRWest, or(hasHookshot, hasSomaria));
453 const canEnterTRBack = when(isInverted,
454         and(eastDeathMountain, hasMirror),
455         neverAvailable,
456 );
457 const laserBridge = or(
458         and(
459                 or(canEnterTRFront, canEnterTRWest, canEnterTREast),
460                 canDarkRoom,
461                 hasSomaria,
462                 hasBig('tr'),
463                 hasSmall('tr', 3),
464         ),
465         canEnterTRBack,
466 );
467
468 // Misc Macros
469
470 const paradoxLower = and(eastDeathMountain, or(canBomb, hasBoom, canShootArrows, hasSomaria));
471
472 const canBridgeRedBomb = or(
473         and(hasMoonpearl, hasHammer),
474         and(agaDead, hasMirror),
475 );
476
477 const canRescueSmith = and(westDarkWorld, hasMoonpearl, canHeavyLift);
478
479 const Logic = {};
480
481 Logic.dungeonInterior = {
482         'hc-boom': fromBool(and(hasSmall('hc'), canKill())),
483         'hc-cell': fromBool(and(hasSmall('hc'), canKill())),
484         'dark-cross': fromBool(canTorchDarkRoom),
485         'sewers-left': fromBool(or(canLift, and(canTorchDarkRoom, hasSmall('hc'), canKill()))),
486         'sewers-mid': fromBool(or(canLift, and(canTorchDarkRoom, hasSmall('hc'), canKill()))),
487         'sewers-right': fromBool(or(canLift, and(canTorchDarkRoom, hasSmall('hc'), canKill()))),
488         'ct-1': fromBool(canKill()),
489         'ct-2': fromBool(and(canKill(), hasSmall('ct'), canDarkRoom)),
490         'ct-boss-killable': fromBool(and(
491                 canKill(), hasSmall('ct', 2), canDarkRoom, canPassCurtains, canKillBoss('ct'),
492         )),
493         'gt-tile-room': fromBool(hasSomaria),
494         'gt-compass-tl': fromBool(and(hasSomaria, hasFireRod, hasSmall('gt', 4))),
495         'gt-compass-tr': fromBool(and(hasSomaria, hasFireRod, hasSmall('gt', 4))),
496         'gt-compass-bl': fromBool(and(hasSomaria, hasFireRod, hasSmall('gt', 4))),
497         'gt-compass-br': fromBool(and(hasSomaria, hasFireRod, hasSmall('gt', 4))),
498         'gt-torch': fromBool(canBonk),
499         'gt-dm-tl': fromBool(and(hasHammer, hasHookshot)),
500         'gt-dm-tr': fromBool(and(hasHammer, hasHookshot)),
501         'gt-dm-bl': fromBool(and(hasHammer, hasHookshot)),
502         'gt-dm-br': fromBool(and(hasHammer, hasHookshot)),
503         'gt-map-chest': fromBool(and(hasHammer, or(hasHookshot, canBonk), hasSmall('gt', 4))),
504         'gt-firesnake': fromBool(and(hasHammer, hasHookshot, hasSmall('gt', 3))),
505         'gt-rando-tl': fromBool(and(hasHammer, hasHookshot, hasSmall('gt', 4))),
506         'gt-rando-tr': fromBool(and(hasHammer, hasHookshot, hasSmall('gt', 4))),
507         'gt-rando-bl': fromBool(and(hasHammer, hasHookshot, hasSmall('gt', 4))),
508         'gt-rando-br': fromBool(and(hasHammer, hasHookshot, hasSmall('gt', 4))),
509         'gt-bobs-chest': fromBool(and(
510                 or(and(hasHammer, hasHookshot), and(hasFireRod, hasSomaria)),
511                 hasSmall('gt', 3),
512         )),
513         'gt-ice-left': fromBool(and(
514                 or(and(hasHammer, hasHookshot), and(hasFireRod, hasSomaria)),
515                 hasSmall('gt', 3),
516                 canKillGTBoss('bot'),
517         )),
518         'gt-ice-mid': fromBool(and(
519                 or(and(hasHammer, hasHookshot), and(hasFireRod, hasSomaria)),
520                 hasSmall('gt', 3),
521                 canKillGTBoss('bot'),
522         )),
523         'gt-ice-right': fromBool(and(
524                 or(and(hasHammer, hasHookshot), and(hasFireRod, hasSomaria)),
525                 hasSmall('gt', 3),
526                 canKillGTBoss('bot'),
527         )),
528         'gt-big-chest': fromBool(and(
529                 or(and(hasHammer, hasHookshot), and(hasFireRod, hasSomaria)),
530                 hasSmall('gt', 3),
531                 hasBig('gt'),
532         )),
533         'gt-helma-left': fromBool(and(
534                 hasBig('gt'),
535                 hasSmall('gt', 3),
536                 canShootArrows,
537                 canTorch,
538                 canKillGTBoss('mid'),
539         )),
540         'gt-helma-right': fromBool(and(
541                 hasBig('gt'),
542                 hasSmall('gt', 3),
543                 canShootArrows,
544                 canTorch,
545                 canKillGTBoss('mid'),
546         )),
547         'gt-pre-moldorm': fromBool(and(
548                 hasBig('gt'),
549                 hasSmall('gt', 3),
550                 canShootArrows,
551                 canTorch,
552                 canKillGTBoss('mid'),
553         )),
554         'gt-post-moldorm': fromBool(and(
555                 hasBig('gt'),
556                 hasSmall('gt', 4),
557                 canShootArrows,
558                 canTorch,
559                 canKillGTBoss('mid'),
560                 canKillGTBoss('top'),
561                 hasHookshot,
562         )),
563         'gt-boss-killable': fromBool(and(
564                 hasBig('gt'),
565                 hasSmall('gt', 4),
566                 canShootArrows,
567                 canTorch,
568                 canKillGTBoss('mid'),
569                 canKillGTBoss('top'),
570                 hasHookshot,
571                 canKillBoss('ct'),
572         )),
573         'ep-big-chest': fromBool(hasBig('ep')),
574         'ep-big-key-chest': fromBool(canDarkRoom),
575         'ep-boss-defeated': fromBool(and(
576                 canShootArrows, canTorchDarkRoom, hasBig('ep'), canKillBoss('ep'),
577         )),
578         'dp-big-chest': fromBool(and(canEnterDPFront, hasBig('dp'))),
579         'dp-big-key-chest': fromBool(and(canEnterDPFront, hasSmall('dp'), canKill())),
580         'dp-compass-chest': fromBool(and(canEnterDPFront, hasSmall('dp'))),
581         'dp-map-chest': fromBool(canEnterDPFront),
582         'dp-torch': fromBool(and(canEnterDPFront, canBonk)),
583         'dp-boss-defeated': fromBool(and(
584                 canEnterDPBack,
585                 canTorch,
586                 hasBig('dp'),
587                 hasSmall('dp'),
588                 canKillBoss('dp'),
589         )),
590         'th-basement-cage': fromBool(canFlipSwitches),
591         'th-map-chest': fromBool(canFlipSwitches),
592         'th-big-key-chest': fromBool(and(canFlipSwitches, hasSmall('th'), canTorch)),
593         'th-compass-chest': fromBool(and(canFlipSwitches, hasBig('th'))),
594         'th-big-chest': fromBool(and(canFlipSwitches, hasBig('th'))),
595         'th-boss-defeated': fromBool(and(
596                 canFlipSwitches,
597                 hasBig('th'),
598                 canKillBoss('th'),
599         )),
600         'pd-stalfos-basement': fromBool(or(hasSmall('pd', 1), and(canShootArrows, hasHammer))),
601         'pd-big-key-chest': fromBool(hasSmall('pd', 6)),
602         'pd-arena-bridge': fromBool(or(hasSmall('pd', 1), and(canShootArrows, hasHammer))),
603         'pd-arena-ledge': fromBool(canShootArrows),
604         'pd-map-chest': fromBool(canShootArrows),
605         'pd-compass-chest': fromBool(hasSmall('pd', 4)),
606         'pd-basement-left': fromBool(and(canTorchDarkRoom, hasSmall('pd', 4))),
607         'pd-basement-right': fromBool(and(canTorchDarkRoom, hasSmall('pd', 4))),
608         'pd-harmless-hellway': fromBool(hasSmall('pd', 6)),
609         'pd-maze-top': fromBool(and(canDarkRoom, hasSmall('pd', 6))),
610         'pd-maze-bottom': fromBool(and(canDarkRoom, hasSmall('pd', 6))),
611         'pd-big-chest': fromBool(and(canDarkRoom, hasBig('pd'), hasSmall('pd', 6))),
612         'pd-boss-defeated': fromBool(and(
613                 canDarkRoom, hasBig('pd'), hasSmall('pd', 6), canShootArrows, hasHammer, canKillBoss('pd'),
614         )),
615         'sp-map-chest': fromBool(and(hasSmall('sp'), canBomb)),
616         'sp-big-chest': fromBool(and(hasSmall('sp'), hasHammer, hasBig('sp'))),
617         'sp-compass-chest': fromBool(and(hasSmall('sp'), hasHammer)),
618         'sp-west-chest': fromBool(and(hasSmall('sp'), hasHammer)),
619         'sp-big-key-chest': fromBool(and(hasSmall('sp'), hasHammer)),
620         'sp-flooded-left': fromBool(and(hasSmall('sp'), hasHammer, hasHookshot)),
621         'sp-flooded-right': fromBool(and(hasSmall('sp'), hasHammer, hasHookshot)),
622         'sp-waterfall': fromBool(and(hasSmall('sp'), hasHammer, hasHookshot)),
623         'sp-boss-defeated': fromBool(and(hasSmall('sp'), hasHammer, hasHookshot, canKillBoss('sp'))),
624         'sw-big-chest': fromBool(and(canEnterSWFront, hasBig('sw'))),
625         'sw-bridge-chest': fromBool(canEnterSWBack),
626         'sw-boss-defeated': fromBool(and(
627                 canEnterSWBack, canPassCurtains, hasFireRod, hasSmall('sw', 3), canKillBoss('sw'),
628         )),
629         'tt-attic': fromBool(and(hasBig('tt'), hasSmall('tt'), canBomb)),
630         'tt-cell': fromBool(hasBig('tt')),
631         'tt-big-chest': fromBool(and(hasBig('tt'), hasSmall('tt'), hasHammer)),
632         'tt-boss-defeated': fromBool(and(hasBig('tt'), hasSmall('tt'), canKillBoss('tt'))),
633         'ip-big-key-chest': fromBool(and(rightSideIP, hasHammer, canLift)),
634         'ip-map-chest': fromBool(and(rightSideIP, hasHammer, canLift)),
635         'ip-spike-chest': fromBool(rightSideIP),
636         'ip-freezor-chest': fromBool(canMeltThings),
637         'ip-big-chest': fromBool(hasBig('ip')),
638         'ip-boss-defeated': fromBool(and(
639                 hasBig('ip'),
640                 hasHammer,
641                 canLift,
642                 or(hasSmall('ip', 2), and(hasSomaria, hasSmall('ip'))),
643                 canKillBoss('ip'),
644         )),
645         'mm-lobby-chest': fromBool(or(hasBig('mm'), hasSmall('mm'))),
646         'mm-compass-chest': fromBool(and(canTorch, hasSmall('mm', 3))),
647         'mm-big-key-chest': fromBool(and(canTorch, hasSmall('mm', 3))),
648         'mm-big-chest': fromBool(hasBig('mm')),
649         'mm-map-chest': fromBool(or(hasBig('mm'), hasSmall('mm'))),
650         'mm-boss-defeated': fromBool(and(hasBig('mm'), canDarkRoom, hasSomaria, canKillBoss('mm'))),
651         'tr-roller-left': fromBool(and(hasFireRod, hasSomaria, or(
652                 canEnterTRFront,
653                 and(or(canEnterTRWest, canEnterTREast), hasSmall('tr', 4)),
654                 and(canEnterTRBack, canDarkRoom, hasSmall('tr', 4)),
655         ))),
656         'tr-roller-right': fromBool(and(hasFireRod, hasSomaria, or(
657                 canEnterTRFront,
658                 and(or(canEnterTRWest, canEnterTREast), hasSmall('tr', 4)),
659                 and(canEnterTRBack, canDarkRoom, hasSmall('tr', 4)),
660         ))),
661         'tr-compass-chest': fromBool(and(hasSomaria, or(
662                 canEnterTRFront,
663                 and(or(canEnterTRWest, canEnterTREast), hasSmall('tr', 4)),
664                 and(canEnterTRBack, canDarkRoom, hasSmall('tr', 4)),
665         ))),
666         'tr-chomps': fromBool(or(
667                 and(canEnterTRFront, hasSmall('tr')),
668                 canEnterTRWest,
669                 canEnterTREast,
670                 and(canEnterTRBack, canDarkRoom, hasSomaria),
671         )),
672         'tr-big-key-chest': fromBool(and(hasSmall('tr', 4), or(
673                 and(canEnterTRFront, hasSomaria),
674                 canEnterTRWest,
675                 canEnterTREast,
676                 and(canEnterTRBack, canDarkRoom, hasSomaria),
677         ))),
678         'tr-big-chest': fromBool(canEnterTREast),
679         'tr-crysta-roller': fromBool(or(
680                 and(hasBig('tr'), or(
681                         and(canEnterTRFront, hasSomaria, hasSmall('tr', 2)),
682                         canEnterTRWest,
683                         canEnterTREast,
684                 )),
685                 and(canEnterTRBack, canDarkRoom, hasSomaria),
686         )),
687         'tr-laser-bridge-top': fromBool(laserBridge),
688         'tr-laser-bridge-left': fromBool(laserBridge),
689         'tr-laser-bridge-right': fromBool(laserBridge),
690         'tr-laser-bridge-bottom': fromBool(laserBridge),
691         'tr-boss-defeated': fromBool(and(
692                 laserBridge,
693                 hasSmall('tr', 4),
694                 hasBig('tr'),
695                 hasSomaria,
696                 canKillBoss('tr'),
697         )),
698 };
699
700 Logic.open = {
701         fallback: fromBool(alwaysAvailable),
702         aginah: fromBool(canBomb),
703         blacksmith: fromBool(canRescueSmith),
704         'blinds-hut-top': fromBool(canBomb),
705         'bombos-tablet': fromBool(and(southDarkWorld, hasMirror, canTablet)),
706         'bonk-rocks': fromBool(canBonk),
707         brewery: fromBool(and(westDarkWorld, canBomb, hasMoonpearl)),
708         'bumper-cave': fromBool(and(westDarkWorld, hasMoonpearl, canLift, hasCape)),
709         'c-house': fromBool(and(westDarkWorld, hasMoonpearl)),
710         catfish: fromBool(and(eastDarkWorld, hasMoonpearl, canLift)),
711         'cave-45': fromBool(and(southDarkWorld, hasMirror)),
712         checkerboard: fromBool(and(mireArea, hasMirror)),
713         'chest-game': fromBool(and(westDarkWorld, hasMoonpearl)),
714         'chicken-house': fromBool(canBomb),
715         'desert-ledge': fromBool(canEnterDPFront),
716         'digging-game': fromBool(and(southDarkWorld, hasMoonpearl)),
717         'ether-tablet': fromBool(and(northDeathMountain, canTablet)),
718         'floating-island': fromBool(
719                 and(eastDarkDeathMountain, hasMoonpearl, canLift, canBomb, hasMirror),
720         ),
721         'flute-spot': fromBool(hasShovel),
722         'graveyard-ledge': fromBool(and(westDarkWorld, hasMoonpearl, hasMirror)),
723         'hammer-pegs': fromBool(and(westDarkWorld, hasHammer, hasMoonpearl, canHeavyLift)),
724         hobo: fromBool(canSwim),
725         'hookshot-cave-tl': fromBool(and(eastDarkDeathMountain, hasMoonpearl, canLift, hasHookshot)),
726         'hookshot-cave-tr': fromBool(and(eastDarkDeathMountain, hasMoonpearl, canLift, hasHookshot)),
727         'hookshot-cave-bl': fromBool(and(eastDarkDeathMountain, hasMoonpearl, canLift, hasHookshot)),
728         'hookshot-cave-br': fromBool(
729                 and(eastDarkDeathMountain, hasMoonpearl, canLift, or(hasHookshot, canBonk)),
730         ),
731         'hype-cave-npc': fromBool(and(southDarkWorld, hasMoonpearl, canBomb)),
732         'hype-cave-top': fromBool(and(southDarkWorld, hasMoonpearl, canBomb)),
733         'hype-cave-right': fromBool(and(southDarkWorld, hasMoonpearl, canBomb)),
734         'hype-cave-left': fromBool(and(southDarkWorld, hasMoonpearl, canBomb)),
735         'hype-cave-bottom': fromBool(and(southDarkWorld, hasMoonpearl, canBomb)),
736         'ice-rod-cave': fromBool(canBomb),
737         'kak-well-top': fromBool(canBomb),
738         'kings-tomb': fromBool(and(canBonk, or(canHeavyLift, and(westDarkWorld, hasMirror)))),
739         'lake-hylia-island': fromBool(
740                 and(canSwim, hasMirror, hasMoonpearl, or(eastDarkWorld, southDarkWorld)),
741         ),
742         library: fromBool(canBonk),
743         lumberjack: fromBool(and(canBonk, agaDead)),
744         'magic-bat': fromBool(and(hasPowder,
745                 or(hasHammer, and(westDarkWorld, hasMoonpearl, canHeavyLift, hasMirror)),
746         )),
747         'mimic-cave': fromBool(and(canEnterTRFront, canBomb, hasSmall('tr', 2), hasMirror, hasHammer)),
748         'mini-moldorm-left': fromBool(canBomb),
749         'mini-moldorm-right': fromBool(canBomb),
750         'mini-moldorm-far-left': fromBool(canBomb),
751         'mini-moldorm-far-right': fromBool(canBomb),
752         'mini-moldorm-npc': fromBool(canBomb),
753         'mire-shed-left': fromBool(and(mireArea, hasMoonpearl)),
754         'mire-shed-right': fromBool(and(mireArea, hasMoonpearl)),
755         'old-man': fromBool(and(westDeathMountain, canDarkRoom)),
756         'paradox-lower-far-left': fromBool(paradoxLower),
757         'paradox-lower-left': fromBool(paradoxLower),
758         'paradox-lower-right': fromBool(paradoxLower),
759         'paradox-lower-far-right': fromBool(paradoxLower),
760         'paradox-lower-mid': fromBool(paradoxLower),
761         'paradox-upper-left': fromBool(and(eastDeathMountain, canBomb)),
762         'paradox-upper-right': fromBool(and(eastDeathMountain, canBomb)),
763         pedestal: fromBool(hasPendants(3)),
764         'potion-shop': fromBool(hasMushroom),
765         'purple-chest': fromBool(and(canRescueSmith, hasMoonpearl, canHeavyLift)),
766         pyramid: fromBool(eastDarkWorld),
767         'pyramid-fairy-left': fromBool(and(hasRedCrystals(2), southDarkWorld, canBridgeRedBomb)),
768         'pyramid-fairy-right': fromBool(and(hasRedCrystals(2), southDarkWorld, canBridgeRedBomb)),
769         'race-game': fromBool(or(canBomb, canBonk)),
770         saha: fromBool(hasGreenPendant),
771         'saha-left': fromBool(or(canBomb, canBonk)),
772         'saha-mid': fromBool(or(canBomb, canBonk)),
773         'saha-right': fromBool(or(canBomb, canBonk)),
774         'sick-kid': fromBool(hasBottle(1)),
775         'spec-rock': fromBool(and(westDeathMountain, hasMirror)),
776         'spec-rock-cave': fromBool(westDeathMountain),
777         'spike-cave': fromBool(and(
778                 westDarkDeathMountain,
779                 hasMoonpearl,
780                 hasHammer,
781                 canLift,
782                 or(hasByrna, and(hasCape, hasMagicBars(2))),
783         )),
784         'spiral-cave': fromBool(eastDeathMountain),
785         stumpy: fromBool(and(southDarkWorld, hasMoonpearl)),
786         'super-bunny-top': fromBool(and(eastDarkDeathMountain, hasMoonpearl)),
787         'super-bunny-bottom': fromBool(and(eastDarkDeathMountain, hasMoonpearl)),
788         'waterfall-fairy-left': fromBool(canSwim),
789         'waterfall-fairy-right': fromBool(canSwim),
790         zora: fromBool(or(canLift, canSwim)),
791         'zora-ledge': fromBool(canSwim),
792         ct: fromBool(canEnterCT),
793         gt: fromBool(canEnterGT),
794         dp: fromBool(or(canEnterDPFront, canEnterDPBack)),
795         th: fromBool(canEnterTH),
796         pd: fromBool(canEnterPD),
797         sp: fromBool(canEnterSP),
798         sw: fromBool(or(canEnterSWFront, canEnterSWMid, canEnterSWBack)),
799         tt: fromBool(canEnterTT),
800         ip: fromBool(canEnterIP),
801         mm: fromBool(canEnterMM),
802         tr: fromBool(or(canEnterTRFront, canEnterTRWest, canEnterTREast, canEnterTRBack)),
803         ...Logic.dungeonInterior,
804 };
805
806 Logic.inverted = {
807         fallback: fromBool(alwaysAvailable),
808         aginah: fromBool(and(southLightWorld, hasMoonpearl, canBomb)),
809         blacksmith: fromBool(and(or(canHeavyLift, hasMirror), westLightWorld)),
810         'blinds-hut-top': fromBool(and(westLightWorld, hasMoonpearl, canBomb)),
811         'blinds-hut-far-left': fromBool(and(westLightWorld, hasMoonpearl)),
812         'blinds-hut-left': fromBool(and(westLightWorld, hasMoonpearl)),
813         'blinds-hut-right': fromBool(and(westLightWorld, hasMoonpearl)),
814         'blinds-hut-far-right': fromBool(and(westLightWorld, hasMoonpearl)),
815         'bombos-tablet': fromBool(and(southLightWorld, canTablet)),
816         'bonk-rocks': fromBool(and(westLightWorld, hasMoonpearl, canBonk)),
817         'bottle-vendor': fromBool(westLightWorld),
818         brewery: fromBool(canBomb),
819         'bumper-cave': fromBool(and(canLift, hasCape, hasMoonpearl, hasMirror, westLightWorld)),
820         catfish: fromBool(or(
821                 and(eastDarkWorld, canLift),
822                 and(hasMirror, southLightWorld, hasMoonpearl, canSwim),
823         )),
824         'cave-45': fromBool(and(southLightWorld, hasMoonpearl)),
825         checkerboard: fromBool(and(southLightWorld, hasMoonpearl, canLift)),
826         'chicken-house': fromBool(and(westLightWorld, hasMoonpearl, canBomb)),
827         'desert-ledge': fromBool(and(hasMoonpearl, canEnterDPFront)),
828         'ether-tablet': fromBool(and(northDeathMountain, canTablet)),
829         'floating-island': fromBool(and(eastDeathMountain)),
830         'flooded-chest': fromBool(and(southLightWorld, hasMoonpearl)),
831         'flute-spot': fromBool(and(southLightWorld, hasMoonpearl, hasShovel)),
832         'graveyard-ledge': fromBool(and(westLightWorld, hasMoonpearl)),
833         'hammer-pegs': fromBool(and(hasHammer, or(canHeavyLift, and(westLightWorld, hasMirror)))),
834         hobo: fromBool(and(southLightWorld, hasMoonpearl, canSwim)),
835         'hookshot-cave-tl': fromBool(and(
836                 eastDarkDeathMountain,
837                 hasHookshot,
838                 or(canLift, and(canBomb, hasMirror, eastDeathMountain)),
839         )),
840         'hookshot-cave-tr': fromBool(and(
841                 eastDarkDeathMountain,
842                 hasHookshot,
843                 or(canLift, and(canBomb, hasMirror, eastDeathMountain)),
844         )),
845         'hookshot-cave-bl': fromBool(and(
846                 eastDarkDeathMountain,
847                 hasHookshot,
848                 or(canLift, and(canBomb, hasMirror, eastDeathMountain)),
849         )),
850         'hookshot-cave-br': fromBool(and(
851                 eastDarkDeathMountain,
852                 or(canBonk, hasHookshot),
853                 or(canLift, and(canBomb, hasMirror, eastDeathMountain)),
854         )),
855         'hype-cave-npc': fromBool(canBomb),
856         'hype-cave-top': fromBool(canBomb),
857         'hype-cave-right': fromBool(canBomb),
858         'hype-cave-left': fromBool(canBomb),
859         'hype-cave-bottom': fromBool(canBomb),
860         'ice-rod-cave': fromBool(and(southLightWorld, hasMoonpearl, canBomb)),
861         'kak-well-top': fromBool(and(westLightWorld, hasMoonpearl, canBomb)),
862         'kak-well-left': fromBool(and(westLightWorld, hasMoonpearl)),
863         'kak-well-mid': fromBool(and(westLightWorld, hasMoonpearl)),
864         'kak-well-right': fromBool(and(westLightWorld, hasMoonpearl)),
865         'kak-well-bottom': fromBool(and(westLightWorld, hasMoonpearl)),
866         'kings-tomb': fromBool(and(westLightWorld, hasMoonpearl, canBonk, canHeavyLift)),
867         'lake-hylia-island': fromBool(
868                 and(southLightWorld, hasMoonpearl, canSwim),
869         ),
870         library: fromBool(and(southLightWorld, hasMoonpearl, canBonk)),
871         lumberjack: fromBool(and(westLightWorld, hasMoonpearl, canBonk, agaDead)),
872         'lost-woods-hideout': fromBool(and(westLightWorld, hasMoonpearl)),
873         'magic-bat': fromBool(and(westLightWorld, hasMoonpearl, hasPowder, hasHammer)),
874         'maze-race': fromBool(and(southLightWorld, hasMoonpearl, or(canBomb, canBonk))),
875         'mimic-cave': fromBool(and(
876                 eastDeathMountain,
877                 hasMoonpearl,
878                 hasHammer,
879         )),
880         'mini-moldorm-far-left': fromBool(and(southLightWorld, hasMoonpearl, canBomb, canKill)),
881         'mini-moldorm-left': fromBool(and(southLightWorld, hasMoonpearl, canBomb, canKill)),
882         'mini-moldorm-right': fromBool(and(southLightWorld, hasMoonpearl, canBomb, canKill)),
883         'mini-moldorm-far-right': fromBool(and(southLightWorld, hasMoonpearl, canBomb, canKill)),
884         'mini-moldorm-npc': fromBool(and(southLightWorld, hasMoonpearl, canBomb, canKill)),
885         'mire-shed-left': fromBool(mireArea),
886         'mire-shed-right': fromBool(mireArea),
887         'mushroom-spot': fromBool(and(westLightWorld, hasMoonpearl)),
888         'old-man': fromBool(and(westDeathMountain, canDarkRoom)),
889         'paradox-lower-far-left': fromBool(and(hasMoonpearl, paradoxLower)),
890         'paradox-lower-left': fromBool(and(hasMoonpearl, paradoxLower)),
891         'paradox-lower-mid': fromBool(and(hasMoonpearl, paradoxLower)),
892         'paradox-lower-right': fromBool(and(hasMoonpearl, paradoxLower)),
893         'paradox-lower-far-right': fromBool(and(hasMoonpearl, paradoxLower)),
894         'paradox-upper-left': fromBool(and(eastDeathMountain, hasMoonpearl, canBomb)),
895         'paradox-upper-right': fromBool(and(eastDeathMountain, hasMoonpearl, canBomb)),
896         pedestal: fromBool(and(westLightWorld, hasPendants(3))),
897         'potion-shop': fromBool(and(eastLightWorld, hasMushroom, hasMoonpearl)),
898         'purple-chest': fromBool(and(or(canHeavyLift, hasMirror), westLightWorld, southLightWorld)),
899         pyramid: fromBool(eastDarkWorld),
900         'pyramid-fairy-left': fromBool(and(hasRedCrystals(2), hasMirror)),
901         'pyramid-fairy-right': fromBool(and(hasRedCrystals(2), hasMirror)),
902         'race-game': fromBool(and(westLightWorld, hasMoonpearl, or(canBomb, canBonk))),
903         saha: fromBool(and(eastLightWorld, hasGreenPendant)),
904         'saha-left': fromBool(and(eastLightWorld, hasMoonpearl, or(canBomb, canBonk))),
905         'saha-mid': fromBool(and(eastLightWorld, hasMoonpearl, or(canBomb, canBonk))),
906         'saha-right': fromBool(and(eastLightWorld, or(canBomb, canBonk))),
907         'secret-passage': fromBool(and(eastLightWorld, hasMoonpearl)),
908         'sick-kid': fromBool(and(westLightWorld, hasBottle(1))),
909         'spec-rock': fromBool(northDeathMountain),
910         'spec-rock-cave': fromBool(westDeathMountain),
911         'spike-cave': fromBool(and(
912                 westDarkDeathMountain,
913                 hasHammer,
914                 canLift,
915                 or(hasByrna, and(hasCape, hasMagicBars(2))),
916         )),
917         'spiral-cave': fromBool(and(
918                 eastDeathMountain,
919                 hasMoonpearl,
920         )),
921         'sunken-treasure': fromBool(and(southLightWorld, hasMoonpearl)),
922         'super-bunny-top': fromBool(eastDarkDeathMountain),
923         'super-bunny-bottom': fromBool(eastDarkDeathMountain),
924         tavern: fromBool(and(westLightWorld, hasMoonpearl)),
925         uncle: fromBool(and(eastLightWorld, hasMoonpearl)),
926         'waterfall-fairy-left': fromBool(and(eastLightWorld, hasMoonpearl, canSwim)),
927         'waterfall-fairy-right': fromBool(and(eastLightWorld, hasMoonpearl, canSwim)),
928         zora: fromBool(and(eastLightWorld, hasMoonpearl, or(canLift, canSwim))),
929         'zora-ledge': fromBool(and(eastLightWorld, hasMoonpearl, canSwim)),
930         hc: fromBool(canEnterHC),
931         ct: fromBool(canEnterCT),
932         gt: fromBool(canEnterGT),
933         ep: fromBool(canEnterEP),
934         dp: fromBool(canEnterDPFront),
935         th: fromBool(canEnterTH),
936         pd: fromBool(canEnterPD),
937         sp: fromBool(canEnterSP),
938         sw: fromBool(or(canEnterSWFront, canEnterSWMid, canEnterSWBack)),
939         tt: fromBool(canEnterTT),
940         ip: fromBool(canEnterIP),
941         mm: fromBool(canEnterMM),
942         tr: fromBool(or(canEnterTRFront, canEnterTRWest, canEnterTREast, canEnterTRBack)),
943         ...Logic.dungeonInterior,
944 };
945
946 export default Logic;