255 lines
		
	
	
		
			13 KiB
		
	
	
	
		
			Python
		
	
	
	
			
		
		
	
	
			255 lines
		
	
	
		
			13 KiB
		
	
	
	
		
			Python
		
	
	
	
from random import Random
 | 
						|
from typing import List, Dict, Union
 | 
						|
 | 
						|
from .data.bundle_data import *
 | 
						|
from .logic import StardewLogic
 | 
						|
from .options import BundleRandomization, BundlePrice
 | 
						|
 | 
						|
vanilla_bundles = {
 | 
						|
    "Pantry/0": "Spring Crops/O 465 20/24 1 0 188 1 0 190 1 0 192 1 0/0",
 | 
						|
    "Pantry/1": "Summer Crops/O 621 1/256 1 0 260 1 0 258 1 0 254 1 0/3",
 | 
						|
    "Pantry/2": "Fall Crops/BO 10 1/270 1 0 272 1 0 276 1 0 280 1 0/2",
 | 
						|
    "Pantry/3": "Quality Crops/BO 15 1/24 5 2 254 5 2 276 5 2 270 5 2/6/3",
 | 
						|
    "Pantry/4": "Animal/BO 16 1/186 1 0 182 1 0 174 1 0 438 1 0 440 1 0 442 1 0/4/5",
 | 
						|
    # 639 1 0 640 1 0 641 1 0 642 1 0 643 1 0
 | 
						|
    "Pantry/5": "Artisan/BO 12 1/432 1 0 428 1 0 426 1 0 424 1 0 340 1 0 344 1 0 613 1 0 634 1 0 635 1 0 636 1 0 637 1 0 638 1 0/1/6",
 | 
						|
    "Crafts Room/13": "Spring Foraging/O 495 30/16 1 0 18 1 0 20 1 0 22 1 0/0",
 | 
						|
    "Crafts Room/14": "Summer Foraging/O 496 30/396 1 0 398 1 0 402 1 0/3",
 | 
						|
    "Crafts Room/15": "Fall Foraging/O 497 30/404 1 0 406 1 0 408 1 0 410 1 0/2",
 | 
						|
    "Crafts Room/16": "Winter Foraging/O 498 30/412 1 0 414 1 0 416 1 0 418 1 0/6",
 | 
						|
    "Crafts Room/17": "Construction/BO 114 1/388 99 0 388 99 0 390 99 0 709 10 0/4",
 | 
						|
    "Crafts Room/19": "Exotic Foraging/O 235 5/88 1 0 90 1 0 78 1 0 420 1 0 422 1 0 724 1 0 725 1 0 726 1 0 257 1 0/1/5",
 | 
						|
    "Fish Tank/6": "River Fish/O 685 30/145 1 0 143 1 0 706 1 0 699 1 0/6",
 | 
						|
    "Fish Tank/7": "Lake Fish/O 687 1/136 1 0 142 1 0 700 1 0 698 1 0/0",
 | 
						|
    "Fish Tank/8": "Ocean Fish/O 690 5/131 1 0 130 1 0 150 1 0 701 1 0/5",
 | 
						|
    "Fish Tank/9": "Night Fishing/R 516 1/140 1 0 132 1 0 148 1 0/1",
 | 
						|
    "Fish Tank/10": "Specialty Fish/O 242 5/128 1 0 156 1 0 164 1 0 734 1 0/4",
 | 
						|
    "Fish Tank/11": "Crab Pot/O 710 3/715 1 0 716 1 0 717 1 0 718 1 0 719 1 0 720 1 0 721 1 0 722 1 0 723 1 0 372 1 0/1/5",
 | 
						|
    "Boiler Room/20": "Blacksmith's/BO 13 1/334 1 0 335 1 0 336 1 0/2",
 | 
						|
    "Boiler Room/21": "Geologist's/O 749 5/80 1 0 86 1 0 84 1 0 82 1 0/1",
 | 
						|
    "Boiler Room/22": "Adventurer's/R 518 1/766 99 0 767 10 0 768 1 0 769 1 0/1/2",
 | 
						|
    "Vault/23": "2,500g/O 220 3/-1 2500 2500/4",
 | 
						|
    "Vault/24": "5,000g/O 369 30/-1 5000 5000/2",
 | 
						|
    "Vault/25": "10,000g/BO 9 1/-1 10000 10000/3",
 | 
						|
    "Vault/26": "25,000g/BO 21 1/-1 25000 25000/1",
 | 
						|
    "Bulletin Board/31": "Chef's/O 221 3/724 1 0 259 1 0 430 1 0 376 1 0 228 1 0 194 1 0/4",
 | 
						|
    "Bulletin Board/32": "Field Research/BO 20 1/422 1 0 392 1 0 702 1 0 536 1 0/5",
 | 
						|
    "Bulletin Board/33": "Enchanter's/O 336 5/725 1 0 348 1 0 446 1 0 637 1 0/1",
 | 
						|
    "Bulletin Board/34": "Dye/BO 25 1/420 1 0 397 1 0 421 1 0 444 1 0 62 1 0 266 1 0/6",
 | 
						|
    "Bulletin Board/35": "Fodder/BO 104 1/262 10 0 178 10 0 613 3 0/3",
 | 
						|
    # "Abandoned Joja Mart/36": "The Missing//348 1 1 807 1 0 74 1 0 454 5 2 795 1 2 445 1 0/1/5"
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
class Bundle:
 | 
						|
    room: str
 | 
						|
    sprite: str
 | 
						|
    original_name: str
 | 
						|
    name: str
 | 
						|
    rewards: List[str]
 | 
						|
    requirements: List[BundleItem]
 | 
						|
    color: str
 | 
						|
    number_required: int
 | 
						|
 | 
						|
    def __init__(self, key: str, value: str):
 | 
						|
        key_parts = key.split("/")
 | 
						|
        self.room = key_parts[0]
 | 
						|
        self.sprite = key_parts[1]
 | 
						|
 | 
						|
        value_parts = value.split("/")
 | 
						|
        self.original_name = value_parts[0]
 | 
						|
        self.name = value_parts[0]
 | 
						|
        self.rewards = self.parse_stardew_objects(value_parts[1])
 | 
						|
        self.requirements = self.parse_stardew_bundle_items(value_parts[2])
 | 
						|
        self.color = value_parts[3]
 | 
						|
        if len(value_parts) > 4:
 | 
						|
            self.number_required = int(value_parts[4])
 | 
						|
        else:
 | 
						|
            self.number_required = len(self.requirements)
 | 
						|
 | 
						|
    def __repr__(self):
 | 
						|
        return f"{self.original_name} -> {repr(self.requirements)}"
 | 
						|
 | 
						|
    def get_name_with_bundle(self) -> str:
 | 
						|
        return f"{self.original_name} Bundle"
 | 
						|
 | 
						|
    def to_pair(self) -> (str, str):
 | 
						|
        key = f"{self.room}/{self.sprite}"
 | 
						|
        str_rewards = ""
 | 
						|
        for reward in self.rewards:
 | 
						|
            str_rewards += f" {reward}"
 | 
						|
        str_rewards = str_rewards.strip()
 | 
						|
        str_requirements = ""
 | 
						|
        for requirement in self.requirements:
 | 
						|
            str_requirements += f" {requirement.item.item_id} {requirement.amount} {requirement.quality}"
 | 
						|
        str_requirements = str_requirements.strip()
 | 
						|
        value = f"{self.name}/{str_rewards}/{str_requirements}/{self.color}/{self.number_required}"
 | 
						|
        return key, value
 | 
						|
 | 
						|
    def remove_rewards(self):
 | 
						|
        self.rewards = []
 | 
						|
 | 
						|
    def change_number_required(self, difference: int):
 | 
						|
        self.number_required = min(len(self.requirements), max(1, self.number_required + difference))
 | 
						|
        if len(self.requirements) == 1 and self.requirements[0].item.item_id == -1:
 | 
						|
            one_fifth = self.requirements[0].amount / 5
 | 
						|
            new_amount = int(self.requirements[0].amount + (difference * one_fifth))
 | 
						|
            self.requirements[0] = BundleItem.money_bundle(new_amount)
 | 
						|
            thousand_amount = int(new_amount / 1000)
 | 
						|
            dollar_amount = str(new_amount % 1000)
 | 
						|
            while len(dollar_amount) < 3:
 | 
						|
                dollar_amount = f"0{dollar_amount}"
 | 
						|
            self.name = f"{thousand_amount},{dollar_amount}g"
 | 
						|
 | 
						|
    def randomize_requirements(self, random: Random,
 | 
						|
                               potential_requirements: Union[List[BundleItem], List[List[BundleItem]]]):
 | 
						|
        if not potential_requirements:
 | 
						|
            return
 | 
						|
 | 
						|
        number_to_generate = len(self.requirements)
 | 
						|
        self.requirements.clear()
 | 
						|
        if number_to_generate > len(potential_requirements):
 | 
						|
            choices: Union[BundleItem, List[BundleItem]] = random.choices(potential_requirements, k=number_to_generate)
 | 
						|
        else:
 | 
						|
            choices: Union[BundleItem, List[BundleItem]] = random.sample(potential_requirements, number_to_generate)
 | 
						|
        for choice in choices:
 | 
						|
            if isinstance(choice, BundleItem):
 | 
						|
                self.requirements.append(choice)
 | 
						|
            else:
 | 
						|
                self.requirements.append(random.choice(choice))
 | 
						|
 | 
						|
    def assign_requirements(self, new_requirements: List[BundleItem]) -> List[BundleItem]:
 | 
						|
        number_to_generate = len(self.requirements)
 | 
						|
        self.requirements.clear()
 | 
						|
        for requirement in new_requirements:
 | 
						|
            self.requirements.append(requirement)
 | 
						|
            if len(self.requirements) >= number_to_generate:
 | 
						|
                return new_requirements[number_to_generate:]
 | 
						|
 | 
						|
    @staticmethod
 | 
						|
    def parse_stardew_objects(string_objects: str) -> List[str]:
 | 
						|
        objects = []
 | 
						|
        if len(string_objects) < 5:
 | 
						|
            return objects
 | 
						|
        rewards_parts = string_objects.split(" ")
 | 
						|
        for index in range(0, len(rewards_parts), 3):
 | 
						|
            objects.append(f"{rewards_parts[index]} {rewards_parts[index + 1]} {rewards_parts[index + 2]}")
 | 
						|
        return objects
 | 
						|
 | 
						|
    @staticmethod
 | 
						|
    def parse_stardew_bundle_items(string_objects: str) -> List[BundleItem]:
 | 
						|
        bundle_items = []
 | 
						|
        parts = string_objects.split(" ")
 | 
						|
        for index in range(0, len(parts), 3):
 | 
						|
            item_id = int(parts[index])
 | 
						|
            bundle_item = BundleItem(all_bundle_items_by_id[item_id].item,
 | 
						|
                                     int(parts[index + 1]),
 | 
						|
                                     int(parts[index + 2]))
 | 
						|
            bundle_items.append(bundle_item)
 | 
						|
        return bundle_items
 | 
						|
 | 
						|
    # Shuffling the Vault doesn't really work with the stardew system in place
 | 
						|
    # shuffle_vault_amongst_themselves(random, bundles)
 | 
						|
 | 
						|
 | 
						|
def get_all_bundles(random: Random, logic: StardewLogic, randomization: BundleRandomization, price: BundlePrice) -> Dict[str, Bundle]:
 | 
						|
    bundles = {}
 | 
						|
    for bundle_key in vanilla_bundles:
 | 
						|
        bundle_value = vanilla_bundles[bundle_key]
 | 
						|
        bundle = Bundle(bundle_key, bundle_value)
 | 
						|
        bundles[bundle.get_name_with_bundle()] = bundle
 | 
						|
 | 
						|
    if randomization == BundleRandomization.option_thematic:
 | 
						|
        shuffle_bundles_thematically(random, bundles)
 | 
						|
    elif randomization == BundleRandomization.option_shuffled:
 | 
						|
        shuffle_bundles_completely(random, logic, bundles)
 | 
						|
 | 
						|
    price_difference = 0
 | 
						|
    if price == BundlePrice.option_very_cheap:
 | 
						|
        price_difference = -2
 | 
						|
    elif price == BundlePrice.option_cheap:
 | 
						|
        price_difference = -1
 | 
						|
    elif price == BundlePrice.option_expensive:
 | 
						|
        price_difference = 1
 | 
						|
 | 
						|
    for bundle_key in bundles:
 | 
						|
        bundles[bundle_key].remove_rewards()
 | 
						|
        bundles[bundle_key].change_number_required(price_difference)
 | 
						|
 | 
						|
    return bundles
 | 
						|
 | 
						|
 | 
						|
def shuffle_bundles_completely(random: Random, logic: StardewLogic, bundles: Dict[str, Bundle]):
 | 
						|
    total_required_item_number = sum(len(bundle.requirements) for bundle in bundles.values())
 | 
						|
    quality_crops_items_set = set(quality_crops_items)
 | 
						|
    all_bundle_items_without_quality_and_money = [item
 | 
						|
                                                  for item in all_bundle_items_except_money
 | 
						|
                                                  if item not in quality_crops_items_set] + \
 | 
						|
                                                 random.sample(quality_crops_items, 10)
 | 
						|
    choices = random.sample(all_bundle_items_without_quality_and_money, total_required_item_number - 4)
 | 
						|
 | 
						|
    items_sorted = sorted(choices, key=lambda x: logic.item_rules[x.item.name].get_difficulty())
 | 
						|
 | 
						|
    keys = sorted(bundles.keys())
 | 
						|
    random.shuffle(keys)
 | 
						|
 | 
						|
    for key in keys:
 | 
						|
        if not bundles[key].original_name.endswith("00g"):
 | 
						|
            items_sorted = bundles[key].assign_requirements(items_sorted)
 | 
						|
 | 
						|
 | 
						|
def shuffle_bundles_thematically(random: Random, bundles: Dict[str, Bundle]):
 | 
						|
    shuffle_crafts_room_bundle_thematically(random, bundles)
 | 
						|
    shuffle_pantry_bundle_thematically(random, bundles)
 | 
						|
    shuffle_fish_tank_thematically(random, bundles)
 | 
						|
    shuffle_boiler_room_thematically(random, bundles)
 | 
						|
    shuffle_bulletin_board_thematically(random, bundles)
 | 
						|
 | 
						|
 | 
						|
def shuffle_crafts_room_bundle_thematically(random: Random, bundles: Dict[str, Bundle]):
 | 
						|
    bundles["Spring Foraging Bundle"].randomize_requirements(random, spring_foraging_items)
 | 
						|
    bundles["Summer Foraging Bundle"].randomize_requirements(random, summer_foraging_items)
 | 
						|
    bundles["Fall Foraging Bundle"].randomize_requirements(random, fall_foraging_items)
 | 
						|
    bundles["Winter Foraging Bundle"].randomize_requirements(random, winter_foraging_items)
 | 
						|
    bundles["Exotic Foraging Bundle"].randomize_requirements(random, exotic_foraging_items)
 | 
						|
    bundles["Construction Bundle"].randomize_requirements(random, construction_items)
 | 
						|
 | 
						|
 | 
						|
def shuffle_pantry_bundle_thematically(random: Random, bundles: Dict[str, Bundle]):
 | 
						|
    bundles["Spring Crops Bundle"].randomize_requirements(random, spring_crop_items)
 | 
						|
    bundles["Summer Crops Bundle"].randomize_requirements(random, summer_crops_items)
 | 
						|
    bundles["Fall Crops Bundle"].randomize_requirements(random, fall_crops_items)
 | 
						|
    bundles["Quality Crops Bundle"].randomize_requirements(random, quality_crops_items)
 | 
						|
    bundles["Animal Bundle"].randomize_requirements(random, animal_product_items)
 | 
						|
    bundles["Artisan Bundle"].randomize_requirements(random, artisan_goods_items)
 | 
						|
 | 
						|
 | 
						|
def shuffle_fish_tank_thematically(random: Random, bundles: Dict[str, Bundle]):
 | 
						|
    bundles["River Fish Bundle"].randomize_requirements(random, river_fish_items)
 | 
						|
    bundles["Lake Fish Bundle"].randomize_requirements(random, lake_fish_items)
 | 
						|
    bundles["Ocean Fish Bundle"].randomize_requirements(random, ocean_fish_items)
 | 
						|
    bundles["Night Fishing Bundle"].randomize_requirements(random, night_fish_items)
 | 
						|
    bundles["Crab Pot Bundle"].randomize_requirements(random, crab_pot_items)
 | 
						|
    bundles["Specialty Fish Bundle"].randomize_requirements(random, specialty_fish_items)
 | 
						|
 | 
						|
 | 
						|
def shuffle_boiler_room_thematically(random: Random, bundles: Dict[str, Bundle]):
 | 
						|
    bundles["Blacksmith's Bundle"].randomize_requirements(random, blacksmith_items)
 | 
						|
    bundles["Geologist's Bundle"].randomize_requirements(random, geologist_items)
 | 
						|
    bundles["Adventurer's Bundle"].randomize_requirements(random, adventurer_items)
 | 
						|
 | 
						|
 | 
						|
def shuffle_bulletin_board_thematically(random: Random, bundles: Dict[str, Bundle]):
 | 
						|
    bundles["Chef's Bundle"].randomize_requirements(random, chef_items)
 | 
						|
    bundles["Dye Bundle"].randomize_requirements(random, dye_items)
 | 
						|
    bundles["Field Research Bundle"].randomize_requirements(random, field_research_items)
 | 
						|
    bundles["Fodder Bundle"].randomize_requirements(random, fodder_items)
 | 
						|
    bundles["Enchanter's Bundle"].randomize_requirements(random, enchanter_items)
 | 
						|
 | 
						|
 | 
						|
def shuffle_vault_amongst_themselves(random: Random, bundles: Dict[str, Bundle]):
 | 
						|
    bundles["2,500g Bundle"].randomize_requirements(random, vault_bundle_items)
 | 
						|
    bundles["5,000g Bundle"].randomize_requirements(random, vault_bundle_items)
 | 
						|
    bundles["10,000g Bundle"].randomize_requirements(random, vault_bundle_items)
 | 
						|
    bundles["25,000g Bundle"].randomize_requirements(random, vault_bundle_items)
 |