13
13
from worlds .AutoWorld import WebWorld , World
14
14
from worlds .generic .Rules import add_item_rule , set_rule
15
15
from .logic import SoEPlayerLogic
16
- from .options import Difficulty , EnergyCore , SoEOptions
16
+ from .options import Difficulty , EnergyCore , Sniffamizer , SniffIngredients , SoEOptions
17
17
from .patch import SoEDeltaPatch , get_base_rom_path
18
18
19
19
if typing .TYPE_CHECKING :
64
64
pyevermizer .CHECK_BOSS : _id_base + 50 , # bosses 64050..6499
65
65
pyevermizer .CHECK_GOURD : _id_base + 100 , # gourds 64100..64399
66
66
pyevermizer .CHECK_NPC : _id_base + 400 , # npc 64400..64499
67
- # TODO: sniff 64500..64799
67
+ # blank 64500..64799
68
68
pyevermizer .CHECK_EXTRA : _id_base + 800 , # extra items 64800..64899
69
69
pyevermizer .CHECK_TRAP : _id_base + 900 , # trap 64900..64999
70
+ pyevermizer .CHECK_SNIFF : _id_base + 1000 # sniff 65000..65592
70
71
}
71
72
72
73
# cache native evermizer items and locations
73
74
_items = pyevermizer .get_items ()
75
+ _sniff_items = pyevermizer .get_sniff_items () # optional, not part of the default location pool
74
76
_traps = pyevermizer .get_traps ()
75
77
_extras = pyevermizer .get_extra_items () # items that are not placed by default
76
78
_locations = pyevermizer .get_locations ()
79
+ _sniff_locations = pyevermizer .get_sniff_locations () # optional, not part of the default location pool
77
80
# fix up texts for AP
78
81
for _loc in _locations :
79
82
if _loc .type == pyevermizer .CHECK_GOURD :
80
- _loc .name = f'{ _loc .name } #{ _loc .index } '
83
+ _loc .name = f"{ _loc .name } #{ _loc .index } "
84
+ for _loc in _sniff_locations :
85
+ if _loc .type == pyevermizer .CHECK_SNIFF :
86
+ _loc .name = f"{ _loc .name } Sniff #{ _loc .index } "
87
+ del _loc
88
+
81
89
# item helpers
82
90
_ingredients = (
83
91
'Wax' , 'Water' , 'Vinegar' , 'Root' , 'Oil' , 'Mushroom' , 'Mud Pepper' , 'Meteorite' , 'Limestone' , 'Iron' ,
@@ -97,7 +105,7 @@ def _match_item_name(item: pyevermizer.Item, substr: str) -> bool:
97
105
def _get_location_mapping () -> typing .Tuple [typing .Dict [str , int ], typing .Dict [int , pyevermizer .Location ]]:
98
106
name_to_id = {}
99
107
id_to_raw = {}
100
- for loc in _locations :
108
+ for loc in itertools . chain ( _locations , _sniff_locations ) :
101
109
ap_id = _id_offset [loc .type ] + loc .index
102
110
id_to_raw [ap_id ] = loc
103
111
name_to_id [loc .name ] = ap_id
@@ -108,7 +116,7 @@ def _get_location_mapping() -> typing.Tuple[typing.Dict[str, int], typing.Dict[i
108
116
def _get_item_mapping () -> typing .Tuple [typing .Dict [str , int ], typing .Dict [int , pyevermizer .Item ]]:
109
117
name_to_id = {}
110
118
id_to_raw = {}
111
- for item in itertools .chain (_items , _extras , _traps ):
119
+ for item in itertools .chain (_items , _sniff_items , _extras , _traps ):
112
120
if item .name in name_to_id :
113
121
continue
114
122
ap_id = _id_offset [item .type ] + item .index
@@ -168,9 +176,9 @@ class SoEWorld(World):
168
176
options : SoEOptions
169
177
settings : typing .ClassVar [SoESettings ]
170
178
topology_present = False
171
- data_version = 4
179
+ data_version = 5
172
180
web = SoEWebWorld ()
173
- required_client_version = (0 , 3 , 5 )
181
+ required_client_version = (0 , 4 , 4 )
174
182
175
183
item_name_to_id , item_id_to_raw = _get_item_mapping ()
176
184
location_name_to_id , location_id_to_raw = _get_location_mapping ()
@@ -238,16 +246,26 @@ def get_sphere_index(evermizer_loc: pyevermizer.Location) -> int:
238
246
spheres .setdefault (get_sphere_index (loc ), {}).setdefault (loc .type , []).append (
239
247
SoELocation (self .player , loc .name , self .location_name_to_id [loc .name ], ingame ,
240
248
loc .difficulty > max_difficulty ))
249
+ # extend pool if feature and setting enabled
250
+ if hasattr (Sniffamizer , "option_everywhere" ) and self .options .sniffamizer == Sniffamizer .option_everywhere :
251
+ for loc in _sniff_locations :
252
+ spheres .setdefault (get_sphere_index (loc ), {}).setdefault (loc .type , []).append (
253
+ SoELocation (self .player , loc .name , self .location_name_to_id [loc .name ], ingame ,
254
+ loc .difficulty > max_difficulty ))
241
255
242
256
# location balancing data
243
257
trash_fills : typing .Dict [int , typing .Dict [int , typing .Tuple [int , int , int , int ]]] = {
244
- 0 : {pyevermizer .CHECK_GOURD : (20 , 40 , 40 , 40 )}, # remove up to 40 gourds from sphere 1
245
- 1 : {pyevermizer .CHECK_GOURD : (70 , 90 , 90 , 90 )}, # remove up to 90 gourds from sphere 2
258
+ 0 : {pyevermizer .CHECK_GOURD : (20 , 40 , 40 , 40 ), # remove up to 40 gourds from sphere 1
259
+ pyevermizer .CHECK_SNIFF : (100 , 130 , 130 , 130 )}, # remove up to 130 sniff spots from sphere 1
260
+ 1 : {pyevermizer .CHECK_GOURD : (70 , 90 , 90 , 90 ), # remove up to 90 gourds from sphere 2
261
+ pyevermizer .CHECK_SNIFF : (160 , 200 , 200 , 200 )}, # remove up to 200 sniff spots from sphere 2
246
262
}
247
263
248
264
# mark some as excluded based on numbers above
249
265
for trash_sphere , fills in trash_fills .items ():
250
266
for typ , counts in fills .items ():
267
+ if typ not in spheres [trash_sphere ]:
268
+ continue # e.g. player does not have sniff locations
251
269
count = counts [self .options .difficulty .value ]
252
270
for location in self .random .sample (spheres [trash_sphere ][typ ], count ):
253
271
assert location .name != "Energy Core #285" , "Error in sphere generation"
@@ -299,6 +317,15 @@ def create_items(self) -> None:
299
317
# remove one pair of wings that will be placed in generate_basic
300
318
items .remove (self .create_item ("Wings" ))
301
319
320
+ # extend pool if feature and setting enabled
321
+ if hasattr (Sniffamizer , "option_everywhere" ) and self .options .sniffamizer == Sniffamizer .option_everywhere :
322
+ if self .options .sniff_ingredients == SniffIngredients .option_vanilla_ingredients :
323
+ # vanilla ingredients
324
+ items += list (map (lambda item : self .create_item (item ), _sniff_items ))
325
+ else :
326
+ # random ingredients
327
+ items += [self .create_item (self .get_filler_item_name ()) for _ in _sniff_items ]
328
+
302
329
def is_ingredient (item : pyevermizer .Item ) -> bool :
303
330
for ingredient in _ingredients :
304
331
if _match_item_name (item , ingredient ):
@@ -345,7 +372,12 @@ def set_rules(self) -> None:
345
372
set_rule (self .multiworld .get_location ('Done' , self .player ),
346
373
lambda state : self .logic .has (state , pyevermizer .P_FINAL_BOSS ))
347
374
set_rule (self .multiworld .get_entrance ('New Game' , self .player ), lambda state : True )
348
- for loc in _locations :
375
+ locations : typing .Iterable [pyevermizer .Location ]
376
+ if hasattr (Sniffamizer , "option_everywhere" ) and self .options .sniffamizer == Sniffamizer .option_everywhere :
377
+ locations = itertools .chain (_locations , _sniff_locations )
378
+ else :
379
+ locations = _locations
380
+ for loc in locations :
349
381
location = self .multiworld .get_location (loc .name , self .player )
350
382
set_rule (location , self .make_rule (loc .requires ))
351
383
0 commit comments