33from random import Random
44from typing import List , Tuple
55
6+ from Options import DeathLink
67from .bundle_item import BundleItem
78from ..content import StardewContent
8- from ..options import BundlePrice , StardewValleyOptions , ExcludeGingerIsland , FestivalLocations
9- from ..strings .currency_names import Currency
9+ from ..options import BundlePrice , StardewValleyOptions , ExcludeGingerIsland , FestivalLocations , TrapDifficulty , \
10+ MultipleDaySleepEnabled , Gifting , EntranceRandomization
11+ from ..strings .bundle_names import MemeBundleName
12+ from ..strings .currency_names import Currency , MemeCurrency
1013
1114
1215@dataclass
@@ -19,6 +22,10 @@ class Bundle:
1922 def __repr__ (self ):
2023 return f"{ self .name } -> { self .number_required } from { repr (self .items )} "
2124
25+ def special_behavior (self , world ):
26+ if self .name == MemeBundleName .clickbait :
27+ world .options .exclude_locations .value .add (MemeBundleName .clickbait )
28+
2229
2330@dataclass
2431class BundleTemplate :
@@ -42,7 +49,42 @@ def extend_from(template, items: List[BundleItem]):
4249 template .number_required_items )
4350
4451 def create_bundle (self , random : Random , content : StardewContent , options : StardewValleyOptions ) -> Bundle :
45- number_required , price_multiplier = get_bundle_final_prices (options .bundle_price , self .number_required_items , False )
52+ try :
53+ number_required , price_multiplier = get_bundle_final_prices (options .bundle_price , self .number_required_items , False )
54+ filtered_items = [item for item in self .items if item .can_appear (content , options )]
55+ number_items = len (filtered_items )
56+ number_chosen_items = self .number_possible_items
57+ if number_chosen_items < number_required :
58+ number_chosen_items = number_required
59+
60+ if number_chosen_items > number_items :
61+ chosen_items = filtered_items + random .choices (filtered_items , k = number_chosen_items - number_items )
62+ else :
63+ chosen_items = random .sample (filtered_items , number_chosen_items )
64+ chosen_items = [item .as_amount (min (999 , max (1 , math .floor (item .amount * price_multiplier )))) for item in chosen_items ]
65+ return Bundle (self .room , self .name , chosen_items , number_required )
66+ except Exception as e :
67+ raise Exception (f"Failed at creating bundle '{ self .name } '. Error: { e } " )
68+
69+
70+ def can_appear (self , options : StardewValleyOptions ) -> bool :
71+ if self .name == MemeBundleName .trap and options .trap_items .value == TrapDifficulty .option_no_traps :
72+ return False
73+ if self .name == MemeBundleName .hibernation and options .multiple_day_sleep_enabled == MultipleDaySleepEnabled .option_false :
74+ return False
75+ if self .name == MemeBundleName .cooperation and options .gifting == Gifting .option_false :
76+ return False
77+ return True
78+
79+
80+ class FixedMultiplierBundleTemplate (BundleTemplate ):
81+
82+ def __init__ (self , room : str , name : str , items : List [BundleItem ], number_possible_items : int ,
83+ number_required_items : int ):
84+ super ().__init__ (room , name , items , number_possible_items , number_required_items )
85+
86+ def create_bundle (self , random : Random , content : StardewContent , options : StardewValleyOptions ) -> Bundle :
87+ number_required = get_number_required_items (options .bundle_price , self .number_required_items )
4688 filtered_items = [item for item in self .items if item .can_appear (content , options )]
4789 number_items = len (filtered_items )
4890 number_chosen_items = self .number_possible_items
@@ -53,11 +95,53 @@ def create_bundle(self, random: Random, content: StardewContent, options: Starde
5395 chosen_items = filtered_items + random .choices (filtered_items , k = number_chosen_items - number_items )
5496 else :
5597 chosen_items = random .sample (filtered_items , number_chosen_items )
56- chosen_items = [item .as_amount (max (1 , math . floor ( item .amount * price_multiplier ))) for item in chosen_items ]
98+ chosen_items = [item .as_amount (min ( 999 , max (1 , item .amount ))) for item in chosen_items ]
5799 return Bundle (self .room , self .name , chosen_items , number_required )
58100
59- def can_appear (self , options : StardewValleyOptions ) -> bool :
60- return True
101+
102+ class FixedPriceBundleTemplate (BundleTemplate ):
103+
104+ def __init__ (self , room : str , name : str , items : List [BundleItem ], number_possible_items : int ,
105+ number_required_items : int ):
106+ super ().__init__ (room , name , items , number_possible_items , number_required_items )
107+
108+ def create_bundle (self , random : Random , content : StardewContent , options : StardewValleyOptions ) -> Bundle :
109+ filtered_items = [item for item in self .items if item .can_appear (content , options )]
110+ number_items = len (filtered_items )
111+ number_chosen_items = self .number_possible_items
112+ if number_chosen_items < self .number_required_items :
113+ number_chosen_items = self .number_required_items
114+
115+ if number_chosen_items > number_items :
116+ chosen_items = filtered_items + random .choices (filtered_items , k = number_chosen_items - number_items )
117+ else :
118+ chosen_items = random .sample (filtered_items , number_chosen_items )
119+ chosen_items = [item .as_amount (max (1 , math .floor (item .amount ))) for item in chosen_items ]
120+ return Bundle (self .room , self .name , chosen_items , self .number_required_items )
121+
122+
123+ # This type of bundle will always match the number of slots and the number of items
124+ class FixedSlotsBundleTemplate (BundleTemplate ):
125+
126+ def __init__ (self , room : str , name : str , items : List [BundleItem ], number_possible_items : int ,
127+ number_required_items : int ):
128+ super ().__init__ (room , name , items , number_possible_items , number_required_items )
129+
130+ def create_bundle (self , random : Random , content : StardewContent , options : StardewValleyOptions ) -> Bundle :
131+ try :
132+ number_required , price_multiplier = get_bundle_final_prices (options .bundle_price , self .number_required_items , False )
133+ filtered_items = [item for item in self .items if item .can_appear (content , options )]
134+ number_items = len (filtered_items )
135+ number_chosen_items = number_required
136+ if number_chosen_items > number_items :
137+ chosen_items = filtered_items + random .choices (filtered_items , k = number_chosen_items - number_items )
138+ else :
139+ chosen_items = random .sample (filtered_items , number_chosen_items )
140+ chosen_items = [item .as_amount (min (999 , max (1 , math .floor (item .amount * price_multiplier )))) for item in chosen_items ]
141+ return Bundle (self .room , self .name , chosen_items , number_required )
142+ except Exception as e :
143+ raise Exception (f"Failed at creating bundle '{ self .name } '. Error: { e } " )
144+
61145
62146
63147class CurrencyBundleTemplate (BundleTemplate ):
@@ -77,15 +161,33 @@ def get_currency_amount(self, bundle_price_option: BundlePrice):
77161 return currency_amount
78162
79163 def can_appear (self , options : StardewValleyOptions ) -> bool :
164+ if not super ().can_appear (options ):
165+ return False
80166 if options .exclude_ginger_island == ExcludeGingerIsland .option_true :
81167 if self .item .item_name == Currency .qi_gem or self .item .item_name == Currency .golden_walnut or self .item .item_name == Currency .cinder_shard :
82168 return False
83169 if options .festival_locations == FestivalLocations .option_disabled :
84170 if self .item .item_name == Currency .star_token :
85171 return False
172+ if options .entrance_randomization != EntranceRandomization .option_disabled :
173+ if self .item .item_name == MemeCurrency .time_elapsed :
174+ return False
175+ if options .death_link != DeathLink .option_true :
176+ if self .item .item_name == MemeCurrency .deathlinks :
177+ return False
86178 return True
87179
88180
181+ class FixedPriceCurrencyBundleTemplate (CurrencyBundleTemplate ):
182+
183+ def __init__ (self , room : str , name : str , item : BundleItem ):
184+ super ().__init__ (room , name , item )
185+
186+ def create_bundle (self , random : Random , content : StardewContent , options : StardewValleyOptions ) -> Bundle :
187+ currency_amount = self .item .amount
188+ return Bundle (self .room , self .name , [BundleItem (self .item .item_name , currency_amount )], 1 )
189+
190+
89191class MoneyBundleTemplate (CurrencyBundleTemplate ):
90192
91193 def __init__ (self , room : str , default_name : str , item : BundleItem ):
@@ -111,11 +213,15 @@ def get_currency_amount(self, bundle_price_option: BundlePrice):
111213
112214class IslandBundleTemplate (BundleTemplate ):
113215 def can_appear (self , options : StardewValleyOptions ) -> bool :
216+ if not super ().can_appear (options ):
217+ return False
114218 return options .exclude_ginger_island == ExcludeGingerIsland .option_false
115219
116220
117221class FestivalBundleTemplate (BundleTemplate ):
118222 def can_appear (self , options : StardewValleyOptions ) -> bool :
223+ if not super ().can_appear (options ):
224+ return False
119225 return options .festival_locations != FestivalLocations .option_disabled
120226
121227
@@ -149,6 +255,96 @@ def create_bundle(self, random: Random, content: StardewContent, options: Starde
149255 return Bundle (self .room , self .name , chosen_items , number_required )
150256
151257
258+ class FixedPriceDeepBundleTemplate (DeepBundleTemplate ):
259+
260+ def __init__ (self , room : str , name : str , categories : List [List [BundleItem ]], number_possible_items : int ,
261+ number_required_items : int ):
262+ super ().__init__ (room , name , categories , number_possible_items , number_required_items )
263+
264+ def create_bundle (self , random : Random , content : StardewContent , options : StardewValleyOptions ) -> Bundle :
265+ number_required = self .number_required_items
266+ number_categories = len (self .categories )
267+ number_chosen_categories = self .number_possible_items
268+ if number_chosen_categories < number_required :
269+ number_chosen_categories = number_required
270+
271+ if number_chosen_categories > number_categories :
272+ chosen_categories = self .categories + random .choices (self .categories ,
273+ k = number_chosen_categories - number_categories )
274+ else :
275+ chosen_categories = random .sample (self .categories , number_chosen_categories )
276+
277+ chosen_items = []
278+ for category in chosen_categories :
279+ filtered_items = [item for item in category if item .can_appear (content , options )]
280+ chosen_items .append (random .choice (filtered_items ))
281+
282+ chosen_items = [item .as_amount (max (1 , math .floor (item .amount ))) for item in chosen_items ]
283+ return Bundle (self .room , self .name , chosen_items , number_required )
284+
285+
286+ @dataclass
287+ class BureaucracyBundleTemplate (BundleTemplate ):
288+
289+ def __init__ (self , room : str , name : str , items : List [BundleItem ], number_possible_items : int ,
290+ number_required_items : int ):
291+ super (BureaucracyBundleTemplate , self ).__init__ (room , name , items , number_possible_items , number_required_items )
292+
293+ def create_bundle (self , random : Random , content : StardewContent , options : StardewValleyOptions ) -> Bundle :
294+ number_required = self .number_required_items + options .bundle_price .value
295+ number_required = min (12 , max (4 , number_required ))
296+ if options .bundle_price == BundlePrice .option_minimum :
297+ number_required = 4
298+ if options .bundle_price == BundlePrice .option_maximum :
299+ number_required = 12
300+ price_multiplier = 1
301+
302+ filtered_items = [item for item in self .items if item .can_appear (content , options )]
303+ number_items = len (filtered_items )
304+ number_chosen_items = self .number_possible_items
305+ if number_chosen_items < number_required :
306+ number_chosen_items = number_required
307+
308+ if number_chosen_items > number_items :
309+ chosen_items = filtered_items + random .choices (filtered_items , k = number_chosen_items - number_items )
310+ else :
311+ chosen_items = random .sample (filtered_items , number_chosen_items )
312+ chosen_items = [item .as_amount (max (1 , math .floor (item .amount * price_multiplier ))) for item in chosen_items ]
313+ return Bundle (self .room , self .name , chosen_items , number_required )
314+
315+
316+ @dataclass
317+ class RecursiveBundleTemplate (BundleTemplate ):
318+ number_sub_bundles : int
319+
320+ def __init__ (self , room : str , name : str , items : List [BundleItem ], number_possible_items : int ,
321+ number_required_items : int , number_sub_bundles : int ):
322+ super (RecursiveBundleTemplate , self ).__init__ (room , name , items , number_possible_items , number_required_items )
323+ self .number_sub_bundles = number_sub_bundles
324+
325+ def create_bundle (self , random : Random , content : StardewContent , options : StardewValleyOptions ) -> Bundle :
326+ number_required = self .number_required_items + (options .bundle_price .value * self .number_sub_bundles )
327+ if options .bundle_price == BundlePrice .option_minimum :
328+ number_required = self .number_sub_bundles
329+ if options .bundle_price == BundlePrice .option_maximum :
330+ number_required = self .number_sub_bundles * 8
331+ number_required = min (self .number_sub_bundles * 8 , max (self .number_sub_bundles , number_required ))
332+ price_multiplier = get_price_multiplier (options .bundle_price , False )
333+
334+ filtered_items = [item for item in self .items if item .can_appear (content , options )]
335+ number_items = len (filtered_items )
336+ number_chosen_items = self .number_possible_items
337+ if number_chosen_items < number_required :
338+ number_chosen_items = number_required
339+
340+ if number_chosen_items > number_items :
341+ chosen_items = filtered_items + random .choices (filtered_items , k = number_chosen_items - number_items )
342+ else :
343+ chosen_items = random .sample (filtered_items , number_chosen_items )
344+ chosen_items = [item .as_amount (max (1 , math .floor (item .amount * price_multiplier ))) for item in chosen_items ]
345+ return Bundle (self .room , self .name , chosen_items , number_required )
346+
347+
152348def get_bundle_final_prices (bundle_price_option : BundlePrice , default_required_items : int , is_currency : bool ) -> Tuple [int , float ]:
153349 number_required_items = get_number_required_items (bundle_price_option , default_required_items )
154350 price_multiplier = get_price_multiplier (bundle_price_option , is_currency )
0 commit comments