-
Notifications
You must be signed in to change notification settings - Fork 33
Expand file tree
/
Copy pathsettings.py
More file actions
410 lines (389 loc) · 30 KB
/
settings.py
File metadata and controls
410 lines (389 loc) · 30 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
import string
from typing import List, Tuple, Optional, Union
import os
class Setting:
def __init__(self, key: str,
category: str, short_key: str, label: str, *,
description: str, aesthetic: bool = False, options: Optional[List[Tuple[str, str, str]]] = None,
default: Optional[Union[bool, float, str]] = None, placeholder: Optional[str] = None,
visible_if: Optional[List[str]] = None):
if options:
assert default in [option_key for option_key, option_short, option_label in options], f"{default} not in {options}"
short_options = set()
for option_key, option_short, option_label in options:
assert option_short != "" or option_key == default, f"No short option for non default {label}:{option_key}"
assert option_short not in short_options, "Duplicate short option value..."
short_options.add(option_short)
self.key = key
self.category = category
self.short_key = short_key
self.label = label
self.description = description
self.aesthetic = aesthetic
self.options = options
self.default = default
self.placeholder = placeholder
self.visible_if = visible_if
self.value = default
def set(self, value):
if isinstance(self.default, bool):
if not isinstance(value, bool):
value = bool(int(value))
elif not isinstance(value, type(self.default)):
try:
value = type(self.default)(value)
except ValueError:
raise ValueError(f"{value} is not an accepted value for {self.key} setting")
if self.options:
if value not in [k for k, s, v in self.options]:
raise ValueError(f"{value} is not an accepted value for {self.key} setting: {','.join(k for k, s, v in self.options)}")
self.value = value
def getShortValue(self):
if self.options:
for option_key, option_short, option_label in self.options:
if self.value == option_key:
return option_short
return str(self.value) + ">"
def toJson(self):
result = {
"key": self.key,
"category": self.category,
"short_key": self.short_key,
"label": self.label,
"description": self.description,
"aesthetic": self.aesthetic,
"default": self.default,
}
if self.options:
result["options"] = [{"key": option_key, "short": option_short, "label": option_label} for option_key, option_short, option_label in self.options]
if self.placeholder:
result["placeholder"] = self.placeholder
if self.visible_if:
result["visible_if"] = self.visible_if
return result
class Settings:
def __init__(self):
gfx_options = [('', '', 'Default')]
gfx_path = os.path.join(os.path.dirname(__file__), "gfx")
for filename in sorted(os.listdir(gfx_path)):
if filename in {"template.png", "template_extended.png", "template_extended2.png"}:
continue
if filename.endswith(".bin"):
gfx_options.append((filename, filename + ">", filename[:-4]))
if filename.endswith(".png") and not filename.endswith(".bin.png"):
gfx_options.append((filename, filename + ">", filename[:-4]))
self.__all = [
Setting('seed', 'Main', '<', 'Seed', placeholder='Leave empty for random seed', default="",
description="""For multiple people to generate the same randomization result, enter the generated seed number here.
Note, not all strings are valid seeds."""),
Setting('logic', 'Main', 'L', 'Logic', options=[('casual', 'c', 'Casual'), ('', '', 'Normal'), ('hard', 'h', 'Hard'), ('glitched', 'g', 'Glitched'), ('hell', 'H', 'Hell')], default='',
description="""Affects where items are allowed to be placed.
[Casual] Same as normal, except that a few more complex options are removed, like removing bushes with powder and killing enemies with powder or bombs.
[Normal] playable without using any tricks or glitches. Requires nothing to be done outside of normal item usage.
[Hard] More advanced techniques may be required, but glitches are not. Examples include tricky jumps, killing enemies with only pots and skipping keys with smart routing.
[Glitched] Advanced glitches and techniques may be required, but extremely difficult or tedious tricks are not required. Examples include Bomb Triggers, Super Jumps and Jesus Jumps.
[Hell] Obscure and hard techniques may be required. Examples include featherless jumping with Pegasus Boots and/or Hookshot, sequential pit buffers and unclipped superjumps. Things in here can be extremely hard to do or very time consuming. Only insane people go for this."""),
Setting('forwardfactor', 'Main', 'F', 'Forward Factor', default=0.0,
description="Forward item weight adjustment factor, lower values generate more rear heavy seeds while higher values generate front heavy seeds. Default is 0.5."),
Setting('accessibility', 'Main', 'A', 'Accessibility', options=[('all', 'a', '100% Locations'), ('goal', 'g', 'Beatable')], default='all',
description="""
[100% Locations] Guaranteed that every single item can be reached and gained.
[Beatable] Only guarantees that the game is beatable. Certain items/chests might never be reachable."""),
Setting('race', 'Main', 'V', 'Race mode', default=False,
description="""
Spoiler logs can not be generated for ROMs generated with race mode enabled, and seed generation is slightly different."""),
# Setting('spoilerformat', 'Main', 'Spoiler Format', options=[('none', 'None'), ('text', 'Text'), ('json', 'JSON')], default='none',
# description="""Affects how the spoiler log is generated.
# [None] No spoiler log is generated. One can still be manually dumped later.
# [Text] Creates a .txt file meant for a human to read.
# [JSON] Creates a .json file with a little more information and meant for a computer to read.""")
Setting('heartpiece', 'Items', 'h', 'Randomize heart pieces', default=True,
description='Includes heart pieces in the item pool'),
Setting('seashells', 'Items', 's', 'Randomize hidden seashells', default=True,
description='Randomizes the secret sea shells hiding in the ground/trees/mansion. (Chests are always randomized.)'),
Setting('heartcontainers', 'Items', 'H', 'Randomize heart containers', default=True,
description='Includes boss heart container drops in the item pool'),
Setting('instruments', 'Items', 'I', 'Randomize instruments', default=False,
description='Instruments are placed on random locations, dungeon goal will just contain a random item.'),
Setting('tradequest', 'Items', 'T', 'Randomize trade quest', default=True,
description='Trade quest items are randomized, each NPC takes its normal trade quest item, but gives a random item.'),
Setting('witch', 'Items', 'W', 'Randomize item given by the witch', default=True,
description='Adds both the Toadstool and the reward for giving the Toadstool to the witch to the item pool.'),
Setting('rooster', 'Items', 'R', 'Add the rooster', default=True,
description='Adds the rooster to the item pool. Without this option, the rooster spot is still a check giving an item. But you will never find the rooster. In that case, any rooster spot is accessible without rooster by other means.'),
Setting('boomerang', 'Items', 'Z', 'Boomerang trade', options=[('default', 'd', 'Normal'), ('trade', 't', 'Trade'), ('gift', 'g', 'Gift')], default='gift',
description="""
[Normal] Requires Magnifying Lens to get the Boomerang.
[Trade] Allows to trade an inventory item for a random other inventory item. The Boomerang is shuffled.
[Gift] You get a random gift of any item, and the Boomerang is shuffled."""),
Setting('dungeon_keys', 'Dungeon Items', 'k', 'Dungeon keys', options=[('', '', 'Standard'),
('keysanity', 'k', 'Keysanity'),
('removed', 'r', 'Removed')], default='',
description="""Sets if dungeon keys can only be in their respective dungeon, or everywhere.
[Standard] Dungeon keys are only in their dungeon.
[Keysanity] Dungeon keys can be anywhere.
[Removed] No keys, key doors are already open. (also known as Keysy)"""),
Setting('nightmare_keys', 'Dungeon Items', 'n', 'Nightmare keys', options=[('', '', 'Standard'),
('keysanity', 'k', 'Keysanity'),
('removed', 'r', 'Removed')],
default='',
description="""Sets if dungeon keys can only be in their respective dungeon, or everywhere.
[Standard] Nightmare keys are only in their dungeon.
[Keysanity] Nightmare keys can be anywhere.
[Removed] No nightmare keys, nightmare doors are already open. (also known as Keysy)"""),
Setting('dungeon_beaks', 'Dungeon Items', 'Y', 'Dungeon beaks', options=[('', '', 'Standard'),
('keysanity', 'k', 'Keysanity'),
('removed', 'r', 'Removed')],
default='',
description="""Sets if dungeon beaks can only be in their respective dungeon, or everywhere.
[Standard] Dungeon beaks are only in their dungeon.
[Keysanity] Dungeon beaks can be anywhere.
[Removed] No beaks."""),
Setting('dungeon_maps', 'Dungeon Items', 'D', 'Dungeon map/compass', options=[('', '', 'Standard'),
('keysanity', 'k', 'Keysanity'),
('removed', 'r', 'Removed')],
default='',
description="""Sets if dungeon maps/compasses can only be in their respective dungeon, or everywhere.
[Standard] Dungeon maps/compasses are only in their dungeon.
[Keysanity] Dungeon maps/compasses can be anywhere.
[Removed] No maps/compasses."""),
Setting('randomstartlocation', 'Entrances', 'r', 'Random start location', default=False,
description='Randomize where your starting house is located'),
Setting('dungeonshuffle', 'Entrances', 'u', 'Dungeon shuffle', default=False,
description='Randomizes the dungeon that each dungeon entrance leads to'),
Setting('entranceshuffle', 'Entrances', 'E', 'Entrance randomizer', options=[("none", '', "Default"), ("simple", 's', "Simple"), ("split", 'S', "Split"), ("mixed", 'm', "Mixed"), ("wild", 'w', "Wild"), ("chaos", "c", "Chaos"), ("insane", 'i', "Insane"), ("madness", 'M', "Madness")], default='none',
description="""Randomizes where overworld entrances lead to.
[Simple] Single entrance caves that contain items are randomized.
[Split] Connector caves are also randomized, in a separate pool from single entrance caves.
[Mixed] Connector caves are also randomized, in the same pool as single entrance caves.
[Wild] Connections can go from overworld to overworld, or inside to inside.
[Chaos] Entrance and exits are decoupled.
[Insane] Combines chaos and wild, anything goes anywhere, there is no God.
[Madness] Even worse then insane, it makes it so multiple entrances can lead to the same location.
If random start location and/or dungeon shuffle is enabled, then these will be shuffled with all the entrances."""),
Setting('shufflejunk', 'Entrances', 'j', 'Shuffle itemless entrances', default=False,
description="Caves/houses without items are also randomized when entrance shuffle is set.",
visible_if=['entranceshuffle', "simple", "split", "mixed", "wild", "chaos", "insane", "madness"]),
Setting('shuffleannoying', 'Entrances', 'a', 'Shuffle annoying entrances', default=False,
description="A few very annoying entrances (Mamu and the Raft House) will also be randomized when entrance shuffle is set.",
visible_if=['entranceshuffle', "simple", "split", "mixed", "wild", "chaos", "insane", "madness"]),
Setting('shufflewater', 'Entrances', 'w', 'Shuffle water entrances', default=False,
description="Entrances that lead to water (Manbo and Damp Cave) will also be randomized when entrance shuffle is set. Use the warp-to-home from the Save & Quit menu if you get stuck (hold A+B+Start+Select until it works).",
visible_if=['entranceshuffle', "simple", "split", "mixed", "wild", "chaos", "insane", "madness"]),
Setting('boss', 'Gameplay', 'B', 'Boss shuffle', options=[('default', '', 'Normal'), ('shuffle', 's', 'Shuffle'), ('random', 'r', 'Randomize')], default='default',
description='Randomizes the dungeon bosses that each dungeon has.'),
Setting('miniboss', 'Gameplay', 'b', 'Miniboss shuffle', options=[('default', '', 'Normal'), ('shuffle', 's', 'Shuffle'), ('random', 'r', 'Randomize')], default='default',
description='Randomizes the dungeon minibosses that each dungeon has.'),
Setting('enemies', 'Gameplay', 'e', 'Enemizer', options=[('default', '', 'None'), ('overworld', 'o', 'Overworld')], default='default',
description='Randomizes which enemies are placed.'),
Setting('goal', 'Gameplay', 'G', 'Goal', options=[('vanilla', 'v', 'Vanilla'), ('instruments', 'i', 'X instruments'), ('specific', 's', 'X specific instruments'),
('open', 'O', 'Egg already open'),
('open-4', '<', 'Random short game (0-4)'), ('5-8', '>', 'Random long game (5-8)'),
('seashells', 'S', 'Seashell hunt (20)'), ('bingo', 'b', 'Bingo!'),
('bingo-double', 'd', 'Double Bingo!'), ('bingo-triple', 't', 'Triple Bingo!'),
('bingo-full', 'B', 'Bingo-25!'), ('maze', 'm', 'Sign Maze')], default='vanilla',
description="""Changes the goal of the game.
[Vanilla] 8 instruments required to open the egg.
[X instruments] A number of instruments required to open the egg.
[X specific instruments] A number of specific instruments required to open the egg. The sign at Mt. Tamaranch will tell you what they are.
[Egg already open] The egg is already open, just head for it once you have the items needed to defeat the boss.
[Randomized instrument count] A random number of instruments required to open the egg, between 0 and 8.
[Random short/long game] A random number of instruments required to open the egg, chosen between 0-4 and 5-8 respectively.
[Seashell hunt] The egg will open once you collected 20 seashells. Instruments are replaced by seashells and shuffled.
[Bingo] Generate a 5x5 bingo board with various goals. Complete one row/column or diagonal to win!
[Double/Triple Bingo] Bingo, but need to complete multiple rows/columns/diagonals to win!
[Bingo-25] Bingo, but need to fill the whole bingo card to win!
[Sign Maze] Go on a long trip on the overworld sign maze to open the egg."""),
Setting('goalcount', 'Gameplay', 'i', 'Goal count', options=[('7', '7', '7 instruments'),
('6', '6', '6 instruments'), ('5', '5', '5 instruments'), ('4', '4', '4 instruments'),
('3', '3', '3 instruments'), ('2', '2', '2 instruments'), ('1', '1', '1 instrument'),
('0', '0', 'No instruments'), ('random', 'R', 'Random instrument count')], default='4',
description="""Amount of instruments to find for the instruments goal.""",
visible_if=["goal", "instruments", "specific"]),
Setting('itempool', 'Gameplay', 'P', 'Item pool', options=[('', '', 'Normal'), ('casual', 'c', 'Casual'), ('pain', 'p', 'Path of Pain'), ('keyup', 'k', 'More keys')], default='',
description="""Effects which items are shuffled.
[Casual] Places more inventory and key items so the seed is easier.
[More keys] Adds more small keys and extra nightmare keys so dungeons are easier.
[Path of pain] ...just find out yourself."""),
Setting('hpmode', 'Gameplay', 'm', 'Health mode', options=[('default', '', 'Normal'), ('inverted', 'i', 'Inverted'), ('1', '1', 'Start with 1 heart'), ('low', 'l', 'Low max'), ('5hit', '5', '5Hit Challenge')], default='default',
description="""
[Normal] Health works as you would expect.
[Inverted] You start with 9 heart containers, but killing a boss will take a heart container instead of giving one.
[Start with 1] Normal game, you just start with 1 heart instead of 3.
[Low max] Replace heart containers with heart pieces.
[5 Hit Challenge] You can take 5 hits before you die, no healing, no saving."""),
Setting('hardmode', 'Gameplay', 'X', 'Hard mode', options=[('none', '', 'Disabled'), ('oracle', 'O', 'Oracle'), ('hero', 'H', 'Hero'), ('ohko', '1', 'One hit KO')], default='none',
description="""
[Oracle] Less iframes and heath from drops. Bombs damage yourself. Water damages you without flippers. No Piece of Power or Guardian Acorn drops.
[Hero] Switch version hero mode, double damage, no heart/fairy drops.
[One hit KO] You die on a single hit, always."""),
Setting('steal', 'Gameplay', 't', 'Stealing from the shop',
options=[('always', 'a', 'Always'), ('never', 'n', 'Never'), ('default', '', 'Normal'),
('ggs', 'g', 'GGS')], default='default',
description="""Effects when you can steal from the shop. Stealing is bad and never in logic.
[Normal] Requires the sword before you can steal.
[Always] You can always steal from the shop.
[Never] You can never steal from the shop.
[GGS] Glitches get stitches, do not try to rob the shopkeeper by S&Q..."""),
Setting('evilshop', 'Special', 'v', 'Evil shop', options=[('', '', 'Disabled'), ('enabled', 'e', 'Enabled')], default='',
description='Replaces the grandpa house with an evil shop, where you can sell heart pieces.'),
Setting('bowwow', 'Special', 'g', 'Good boy mode', options=[('normal', '', 'Disabled'), ('always', 'a', 'Enabled'), ('swordless', 's', 'Enabled (swordless)')], default='normal',
description='Allows Bowwow to be taken into any area, damage bosses and more enemies. If enabled you always start with Bowwow. Swordless option removes the swords from the game and requires you to beat the game without a sword and just Bowwow.'),
Setting('overworld', 'Special', 'O', 'Overworld', options=[('normal', '', 'Normal'), ('dungeondive', 'D', 'Dungeon dive'), ('nodungeons', 'N', 'No dungeons'), ('dungeonchain', 'C', 'Dungeon chain'), ('random', 'R', 'Randomized'), ('alttp', 'A', 'ALttP')], default='normal',
description="""
[Dungeon Dive] Create a different overworld where all the dungeons are directly accessible and almost no chests are located in the overworld.
[No dungeons] All dungeons only consist of a boss fight and a instrument reward. Rest of the dungeon is removed.
[Dungeon Chain] Overworld is fully removed and all dungeons are chained together.
[Random] Creates a randomized overworld. WARNING: This will error out often during generation, work in progress.
[ALttP] Overworld is replaced with one based on A Link to the Past. Also adds the Hammer to the item pool, along with stakes that require the Hammer to remove. WARNING: Work in progress, please report any bugs!"""),
Setting('dungeonchainlength', 'Special', 'd', 'Chain length', options=[
('3', '3', '3 Dungeons'), ('4', '4', '4 Dungeons'), ('5', '5', '5 Dungeons'),
('6', '6', '6 Dungeons'), ('7', '7', '7 Dungeons'), ('8', '8', '8 Dungeons')], default='5',
description="""Amount of dungeons in the dungeon chain.""",
visible_if=["overworld", "dungeonchain"]),
Setting('owlstatues', 'Special', 'o', 'Owl statues', options=[('', '', 'Never'), ('dungeon', 'D', 'In dungeons'), ('overworld', 'O', 'On the overworld'), ('both', 'B', 'Dungeons and Overworld')], default='',
description='Replaces the hints from owl statues with additional randomized items.'),
Setting('keyholesanity', 'Special', 'K', 'Keyhole sanity', default=False,
description='Makes the overworld keyholes give rewards and turns opening dungeons into findable items.'),
Setting('shopsanity', 'Special', 'N', 'Shopsanity', options=[('', '', 'Disabled'), ('basic', 'b', 'Basic'), ('important', 'i', 'Important')], default='',
description='''
Turns all the phone booths into extra shops, and lowers the prices, and allows buying the two shop items independent of each other.
[Basic] Just extra shops.
[Important] Shops are guaranteed to have important items.
'''),
Setting('superweapons', 'Special', 'q', 'Enable super weapons', default=False,
description='All items will be more powerful, faster, harder, bigger stronger. You name it.'),
Setting('quickswap', 'User options', 'Q', 'Quickswap', options=[('none', '', 'Disabled'), ('a', 'a', 'Swap A button'), ('b', 'b', 'Swap B button')], default='none',
description='Adds that the select button swaps with either A or B. The item is swapped with the top inventory slot. The map is not available when quickswap is enabled.',
aesthetic=True),
Setting('textmode', 'User options', 'f', 'Text mode', options=[('fast', '', 'Fast'), ('default', 'd', 'Normal'), ('none', 'n', 'No-text')], default='fast',
description="""[Fast] Makes text appear twice as fast.
[No-Text] Removes all text from the game""", aesthetic=True),
Setting('lowhpbeep', 'User options', 'p', 'Low HP beeps', options=[('none', 'D', 'Disabled'), ('slow', 'S', 'Slow'), ('default', 'N', 'Normal')], default='slow',
description='Slows or disables the low health beeping sound', aesthetic=True),
Setting('noflash', 'User options', 'l', 'Remove flashing lights', default=True,
description='Remove the flashing light effects from Mamu, shopkeeper and MadBatter. Useful for capture cards and people that are sensitive for these things.',
aesthetic=True),
Setting('nagmessages', 'User options', 'S', 'Show nag messages', default=False,
description='Enables the nag messages normally shown when touching stones and crystals.',
aesthetic=True),
Setting('gfxmod', 'User options', 'c', 'Graphics', options=gfx_options, default='',
description='Generally affects at least Link\'s sprite, but can alter any graphics in the game.',
aesthetic=True),
Setting('follower', 'User options', 'x', 'Follower', options=[('', '', 'None'), ('fox', 'f', 'Fox'), ('navi', 'n', 'Navi'), ('ghost', 'g', 'Ghost'), ('yipyip', 'y', 'YipYip')], default='',
description='Gives you a pet follower in the game.',
aesthetic=True),
Setting('linkspalette', 'User options', 'C', "Link's color",
options=[('-1', '-', 'Normal'), ('0', '0', 'Green'), ('1', '1', 'Yellow'), ('2', '2', 'Red'), ('3', '3', 'Blue'),
('4', '4', 'Inverted Red'), ('5', '5', 'Inverted Blue'), ('6', '6', '?? C'), ('7', '7', '?? D')], default='-1', aesthetic=True,
description="""Allows you to force a certain color on Link.
[Normal] Color of Link depends on the tunic.
[Green/Yellow/Red/Blue] Forces Link into one of these colors.
[?? C/D] Colors of Link are usually inverted and color depends on the area you are in."""),
Setting('music', 'User options', 'M', 'Music', options=[('', '', 'Default'), ('random', 'r', 'Random'), ('off', 'o', 'Disable'), ('shifted', 's', 'Tone shifted'), ('ffa', 'f', 'FinalFantasyAdventure')], default='',
description="""
[Random] Randomizes overworld and dungeon music.
[Disable] No music in the whole game.
[Tone shifted] Tone shifts the musics, making it sound different
[FinalFantasyAdventure] Imports various songs from Final Fantasy Adventure""",
aesthetic=True),
]
self.__by_key = {s.key: s for s in self.__all}
# Make sure all short keys are unique
short_keys = {}
for s in self.__all:
assert s.short_key not in short_keys, f"{s.label}: {short_keys[s.short_key].label}"
short_keys[s.short_key] = s
# print("Unused ss:", "".join([s for s in string.ascii_letters if s not in short_keys]))
def __getattr__(self, item):
return self.__by_key[item].value
def __setattr__(self, key, value):
if not key.startswith("_") and key in self.__by_key:
self.__by_key[key].set(value)
else:
super().__setattr__(key, value)
def loadShortString(self, value):
for setting in self.__all:
if isinstance(setting.default, bool):
setting.value = False
index = 0
while index < len(value):
key = value[index]
index += 1
for setting in self.__all:
if setting.short_key != key:
continue
if isinstance(setting.default, bool):
setting.value = True
elif setting.options:
for option_key, option_short, option_label in setting.options:
if option_key != setting.default and value[index:].startswith(option_short):
setting.value = option_key
index += len(option_short)
break
else:
end_of_param = value.find(">", index)
setting.set(value[index:end_of_param])
index = end_of_param + 1
def getShortString(self):
result = ""
for setting in self.__all:
if isinstance(setting.default, bool):
if setting.value:
result += setting.short_key
elif setting.value != setting.default:
result += setting.short_key + setting.getShortValue()
return result
def validate(self):
def req(setting: str, value: str, message: str) -> None:
if getattr(self, setting) != value:
print("Warning: %s (setting adjusted automatically)" % message)
setattr(self, setting, value)
def dis(setting: str, value: str, new_value: str, message: str) -> None:
if getattr(self, setting) == value:
print("Warning: %s (setting adjusted automatically)" % message)
setattr(self, setting, new_value)
if self.goal in ("bingo", "bingo-double", "bingo-triple", "bingo-full"):
req("overworld", "normal", "Bingo goal does not work with dungeondive")
req("accessibility", "all", "Bingo goal needs 'all' accessibility")
dis("steal", "never", "default", "With bingo goal, stealing should be allowed")
dis("boss", "random", "shuffle", "With bingo goal, bosses need to be on normal or shuffle")
dis("miniboss", "random", "shuffle", "With bingo goal, minibosses need to be on normal or shuffle")
dis("itempool", "casual", "", "Bingo goal does not work with casual item pool")
dis("itempool", "keyup", "", "Bingo goal does not work with more keys item pool")
dis("itempool", "pain", "", "Bingo goal does not work with path of pain item pool")
dis("dungeon_maps", "removed", "", "Bingo goal needs maps/compasses")
if self.goal == "maze":
req("overworld", "normal", "Maze goal does not work with dungeondive")
req("accessibility", "all", "Maze goal needs 'all' accessibility")
if self.itempool == "pain":
req("heartpiece", True, "Path of pain removes heart pieces")
if self.overworld == "dungeondive":
dis("owlstatues", "overworld", "", "Dungeon dive does not work with owl statues in overworld")
dis("owlstatues", "both", "dungeon", "Dungeon dive does not work with owl statues in overworld")
if (self.itempool == "pain") & (self.owlstatues == ""):
req("accessibility", "goal", "Dungeon dive does not leave enough rupees in itempool for all accessibility")
if self.overworld == "nodungeons":
dis("owlstatues", "dungeon", "", "No dungeons does not work with owl statues in dungeons")
dis("owlstatues", "both", "overworld", "No dungeons does not work with owl statues in dungeons")
if self.overworld not in {"normal", "nodungeons"}:
req("shopsanity", "", "Shopsanity is only allowed in normal overworlds")
if self.overworld == "alttp":
req("tradequest", True, "ALttP overworld requires trade items")
if self.overworld == "random":
self.goal = "instruments" # Force 4 dungeon goal for random overworld right now.
self.goalcount = "4"
if self.hpmode == '5hit':
req("heartpiece", True, "5hit removes heart pieces")
def set(self, value: str) -> None:
if "=" in value:
key, value = value.split("=", 1)
else:
key, value = value, "1"
if key not in self.__by_key:
raise ValueError(f"Setting {key} not found")
self.__by_key[key].set(value)
def toJson(self):
return [s.toJson() for s in self.__all]
def __iter__(self):
return iter(self.__all)