Merge branch 'main' into tunc-combat-logic

This commit is contained in:
Scipio Wright
2024-07-23 17:43:19 -04:00
committed by GitHub
26 changed files with 394 additions and 275 deletions

View File

@@ -595,8 +595,8 @@ class GameManager(App):
"!help for server commands.")
def connect_button_action(self, button):
self.ctx.username = None
if self.ctx.server:
self.ctx.username = None
async_start(self.ctx.disconnect())
else:
async_start(self.ctx.connect(self.server_connect_bar.text.replace("/connect ", "")))
@@ -836,6 +836,10 @@ class KivyJSONtoTextParser(JSONtoTextParser):
return self._handle_text(node)
def _handle_text(self, node: JSONMessagePart):
# All other text goes through _handle_color, and we don't want to escape markup twice,
# or mess up text that already has intentional markup applied to it
if node.get("type", "text") == "text":
node["text"] = escape_markup(node["text"])
for ref in node.get("refs", []):
node["text"] = f"[ref={self.ref_count}|{ref}]{node['text']}[/ref]"
self.ref_count += 1

View File

@@ -762,7 +762,7 @@ location_table: List[LocationDict] = [
'game_id': "graf385"},
{'name': "Tagged 389 Graffiti Spots",
'stage': Stages.Misc,
'game_id': "graf379"},
'game_id': "graf389"},
]

View File

@@ -140,6 +140,15 @@
painting: True
The Colorful:
painting: True
Welcome Back Area:
room: Welcome Back Area
door: Shortcut to Starting Room
Second Room:
door: Main Door
Hidden Room:
door: Back Right Door
Rhyme Room (Looped Square):
door: Rhyme Room Entrance
panels:
HI:
id: Entry Room/Panel_hi_hi
@@ -3265,7 +3274,6 @@
door: Traveled Entrance
Color Hallways:
door: Color Hallways Entrance
warp: True
panels:
Achievement:
id: Countdown Panels/Panel_traveled_traveled

Binary file not shown.

View File

@@ -159,7 +159,7 @@ def create_regions(world: "LingoWorld") -> None:
RoomAndDoor("Pilgrim Antechamber", "Sun Painting"), EntranceType.PAINTING, False, world)
if early_color_hallways:
connect_entrance(regions, regions["Starting Room"], regions["Outside The Undeterred"], "Early Color Hallways",
connect_entrance(regions, regions["Starting Room"], regions["Color Hallways"], "Early Color Hallways",
None, EntranceType.PAINTING, False, world)
if painting_shuffle:

View File

@@ -29,7 +29,6 @@ class TestPilgrimageWithRoofAndPaintings(LingoTestBase):
"Outside The Undeterred - Green Painting"]
for door in doors:
print(door)
self.assertFalse(self.can_reach_location("Pilgrim Antechamber - PILGRIM"))
self.collect_by_name(door)
@@ -53,7 +52,6 @@ class TestPilgrimageNoRoofYesPaintings(LingoTestBase):
"Starting Room - Street Painting"]
for door in doors:
print(door)
self.assertFalse(self.can_reach_location("Pilgrim Antechamber - PILGRIM"))
self.collect_by_name(door)
@@ -81,13 +79,40 @@ class TestPilgrimageNoRoofNoPaintings(LingoTestBase):
"Orange Tower Fourth Floor - Hot Crusts Door"]
for door in doors:
print(door)
self.assertFalse(self.can_reach_location("Pilgrim Antechamber - PILGRIM"))
self.collect_by_name(door)
self.assertTrue(self.can_reach_location("Pilgrim Antechamber - PILGRIM"))
class TestPilgrimageRequireStartingRoom(LingoTestBase):
options = {
"enable_pilgrimage": "true",
"shuffle_colors": "false",
"shuffle_doors": "complex",
"pilgrimage_allows_roof_access": "false",
"pilgrimage_allows_paintings": "false",
"early_color_hallways": "false"
}
def test_access(self):
doors = ["Second Room - Exit Door", "Crossroads - Roof Access", "Hub Room - Crossroads Entrance",
"Outside The Undeterred - Green Painting", "Outside The Undeterred - Number Hunt",
"Starting Room - Street Painting", "Outside The Initiated - Shortcut to Hub Room",
"Directional Gallery - Shortcut to The Undeterred", "Orange Tower First Floor - Salt Pepper Door",
"Color Hunt - Shortcut to The Steady", "The Bearer - Entrance",
"Orange Tower Fifth Floor - Quadruple Intersection", "The Tenacious - Shortcut to Hub Room",
"Outside The Agreeable - Tenacious Entrance", "Crossroads - Tower Entrance",
"Orange Tower Fourth Floor - Hot Crusts Door", "Challenge Room - Welcome Door",
"Number Hunt - Challenge Entrance", "Welcome Back Area - Shortcut to Starting Room"]
for door in doors:
self.assertFalse(self.can_reach_location("Pilgrim Antechamber - PILGRIM"))
self.collect_by_name(door)
self.assertTrue(self.can_reach_location("Pilgrim Antechamber - PILGRIM"))
class TestPilgrimageYesRoofNoPaintings(LingoTestBase):
options = {
"enable_pilgrimage": "true",
@@ -107,7 +132,6 @@ class TestPilgrimageYesRoofNoPaintings(LingoTestBase):
"Orange Tower Fifth Floor - Quadruple Intersection"]
for door in doors:
print(door)
self.assertFalse(self.can_reach_location("Pilgrim Antechamber - PILGRIM"))
self.collect_by_name(door)

View File

@@ -328,7 +328,7 @@ location_table: List[LocationInfo] = [
{"name": "Boat Rental",
"id": base_id + 55,
"inGameId": "DadDeer[0]",
"needsShovel": False, "purchase": True,
"needsShovel": False, "purchase": 100,
"minGoldenFeathers": 0, "minGoldenFeathersEasy": 0, "minGoldenFeathersBucket": 0},
{"name": "Boat Challenge Reward",
"id": base_id + 56,

View File

@@ -255,7 +255,7 @@ class StardewValleyWorld(World):
Event.victory)
elif self.options.goal == Goal.option_greatest_walnut_hunter:
self.create_event_location(location_table[GoalName.greatest_walnut_hunter],
self.logic.has_walnut(130),
self.logic.walnut.has_walnut(130),
Event.victory)
elif self.options.goal == Goal.option_protector_of_the_valley:
self.create_event_location(location_table[GoalName.protector_of_the_valley],

View File

@@ -3,6 +3,7 @@ from ..game_content import ContentPack, StardewContent
from ...data import villagers_data, fish_data
from ...data.game_item import ItemTag, Tag
from ...data.harvest import ForagingSource, HarvestFruitTreeSource, HarvestCropSource
from ...data.requirement import WalnutRequirement
from ...data.shop import ShopSource
from ...strings.book_names import Book
from ...strings.crop_names import Fruit, Vegetable
@@ -10,7 +11,7 @@ from ...strings.fish_names import Fish
from ...strings.forageable_names import Forageable, Mushroom
from ...strings.fruit_tree_names import Sapling
from ...strings.metal_names import Fossil, Mineral
from ...strings.region_names import Region
from ...strings.region_names import Region, LogicRegion
from ...strings.season_names import Season
from ...strings.seed_names import Seed
@@ -62,6 +63,9 @@ ginger_island_content_pack = GingerIslandContentPack(
Tag(ItemTag.BOOK, ItemTag.BOOK_POWER),
ShopSource(items_price=((10, Mineral.diamond),), shop_region=Region.volcano_dwarf_shop),
),
Book.queen_of_sauce_cookbook: (
Tag(ItemTag.BOOK, ItemTag.BOOK_SKILL),
ShopSource(money_price=50000, shop_region=LogicRegion.bookseller_2, other_requirements=(WalnutRequirement(100),)),), # Worst book ever
},
fishes=(

View File

@@ -290,9 +290,6 @@ pelican_town = ContentPack(
Book.woodcutters_weekly: (
Tag(ItemTag.BOOK, ItemTag.BOOK_SKILL),
ShopSource(money_price=5000, shop_region=LogicRegion.bookseller_1),),
Book.queen_of_sauce_cookbook: (
Tag(ItemTag.BOOK, ItemTag.BOOK_SKILL),
ShopSource(money_price=50000, shop_region=LogicRegion.bookseller_2),), # Worst book ever
},
fishes=(
fish_data.albacore,

View File

@@ -2221,7 +2221,7 @@ id,region,name,tags,mod_name
3817,Shipping,Shipsanity: Raisins,"SHIPSANITY,SHIPSANITY_FULL_SHIPMENT",
3818,Shipping,Shipsanity: Dried Fruit,"SHIPSANITY,SHIPSANITY_FULL_SHIPMENT",
3819,Shipping,Shipsanity: Dried Mushrooms,"SHIPSANITY,SHIPSANITY_FULL_SHIPMENT",
3820,Shipping,Shipsanity: Stardrop Tea,"SHIPSANITY,SHIPSANITY_FULL_SHIPMENT",
3820,Shipping,Shipsanity: Stardrop Tea,"SHIPSANITY",
3821,Shipping,Shipsanity: Prize Ticket,"SHIPSANITY",
3822,Shipping,Shipsanity: Treasure Totem,"SHIPSANITY,REQUIRES_MASTERIES",
3823,Shipping,Shipsanity: Challenge Bait,"SHIPSANITY,REQUIRES_MASTERIES",
@@ -2252,7 +2252,7 @@ id,region,name,tags,mod_name
3848,Shipping,Shipsanity: Way Of The Wind pt. 1,"SHIPSANITY",
3849,Shipping,Shipsanity: Mapping Cave Systems,"SHIPSANITY",
3850,Shipping,Shipsanity: Price Catalogue,"SHIPSANITY",
3851,Shipping,Shipsanity: Queen Of Sauce Cookbook,"SHIPSANITY",
3851,Shipping,Shipsanity: Queen Of Sauce Cookbook,"SHIPSANITY,GINGER_ISLAND",
3852,Shipping,Shipsanity: The Diamond Hunter,"SHIPSANITY,GINGER_ISLAND",
3853,Shipping,Shipsanity: Book of Mysteries,"SHIPSANITY",
3854,Shipping,Shipsanity: Animal Catalogue,"SHIPSANITY",
@@ -2292,7 +2292,7 @@ id,region,name,tags,mod_name
4032,Farm,Read Book Of Stars,"BOOKSANITY,BOOKSANITY_SKILL",
4033,Farm,Read Combat Quarterly,"BOOKSANITY,BOOKSANITY_SKILL",
4034,Farm,Read Mining Monthly,"BOOKSANITY,BOOKSANITY_SKILL",
4035,Farm,Read Queen Of Sauce Cookbook,"BOOKSANITY,BOOKSANITY_SKILL",
4035,Farm,Read Queen Of Sauce Cookbook,"BOOKSANITY,BOOKSANITY_SKILL,GINGER_ISLAND",
4036,Farm,Read Stardew Valley Almanac,"BOOKSANITY,BOOKSANITY_SKILL",
4037,Farm,Read Woodcutter's Weekly,"BOOKSANITY,BOOKSANITY_SKILL",
4051,Museum,Read Tips on Farming,"BOOKSANITY,BOOKSANITY_LOST",
1 id region name tags mod_name
2221 3817 Shipping Shipsanity: Raisins SHIPSANITY,SHIPSANITY_FULL_SHIPMENT
2222 3818 Shipping Shipsanity: Dried Fruit SHIPSANITY,SHIPSANITY_FULL_SHIPMENT
2223 3819 Shipping Shipsanity: Dried Mushrooms SHIPSANITY,SHIPSANITY_FULL_SHIPMENT
2224 3820 Shipping Shipsanity: Stardrop Tea SHIPSANITY,SHIPSANITY_FULL_SHIPMENT SHIPSANITY
2225 3821 Shipping Shipsanity: Prize Ticket SHIPSANITY
2226 3822 Shipping Shipsanity: Treasure Totem SHIPSANITY,REQUIRES_MASTERIES
2227 3823 Shipping Shipsanity: Challenge Bait SHIPSANITY,REQUIRES_MASTERIES
2252 3848 Shipping Shipsanity: Way Of The Wind pt. 1 SHIPSANITY
2253 3849 Shipping Shipsanity: Mapping Cave Systems SHIPSANITY
2254 3850 Shipping Shipsanity: Price Catalogue SHIPSANITY
2255 3851 Shipping Shipsanity: Queen Of Sauce Cookbook SHIPSANITY SHIPSANITY,GINGER_ISLAND
2256 3852 Shipping Shipsanity: The Diamond Hunter SHIPSANITY,GINGER_ISLAND
2257 3853 Shipping Shipsanity: Book of Mysteries SHIPSANITY
2258 3854 Shipping Shipsanity: Animal Catalogue SHIPSANITY
2292 4032 Farm Read Book Of Stars BOOKSANITY,BOOKSANITY_SKILL
2293 4033 Farm Read Combat Quarterly BOOKSANITY,BOOKSANITY_SKILL
2294 4034 Farm Read Mining Monthly BOOKSANITY,BOOKSANITY_SKILL
2295 4035 Farm Read Queen Of Sauce Cookbook BOOKSANITY,BOOKSANITY_SKILL BOOKSANITY,BOOKSANITY_SKILL,GINGER_ISLAND
2296 4036 Farm Read Stardew Valley Almanac BOOKSANITY,BOOKSANITY_SKILL
2297 4037 Farm Read Woodcutter's Weekly BOOKSANITY,BOOKSANITY_SKILL
2298 4051 Museum Read Tips on Farming BOOKSANITY,BOOKSANITY_LOST

View File

@@ -29,3 +29,8 @@ class SeasonRequirement(Requirement):
@dataclass(frozen=True)
class YearRequirement(Requirement):
year: int
@dataclass(frozen=True)
class WalnutRequirement(Requirement):
amount: int

View File

@@ -27,8 +27,8 @@ class BundleLogicMixin(BaseLogicMixin):
self.bundle = BundleLogic(*args, **kwargs)
class BundleLogic(BaseLogic[Union[ReceivedLogicMixin, HasLogicMixin, TimeLogicMixin, RegionLogicMixin, MoneyLogicMixin, QualityLogicMixin, FishingLogicMixin, SkillLogicMixin,
QuestLogicMixin]]):
class BundleLogic(BaseLogic[Union[ReceivedLogicMixin, HasLogicMixin, TimeLogicMixin, RegionLogicMixin, MoneyLogicMixin, QualityLogicMixin, FishingLogicMixin,
SkillLogicMixin, QuestLogicMixin]]):
# Should be cached
def can_complete_bundle(self, bundle: Bundle) -> StardewRule:
item_rules = []
@@ -45,7 +45,7 @@ QuestLogicMixin]]):
qualities.append(bundle_item.quality)
quality_rules = self.get_quality_rules(qualities)
item_rules = self.logic.has_n(*item_rules, count=bundle.number_required)
time_rule = True_() if time_to_grind <= 0 else self.logic.time.has_lived_months(time_to_grind)
time_rule = self.logic.time.has_lived_months(time_to_grind)
return can_speak_junimo & item_rules & quality_rules & time_rule
def get_quality_rules(self, qualities: List[str]) -> StardewRule:

View File

@@ -1,7 +1,6 @@
from __future__ import annotations
import logging
from functools import cached_property
from typing import Collection, Callable
from .ability_logic import AbilityLogicMixin
@@ -43,6 +42,7 @@ from .time_logic import TimeLogicMixin
from .tool_logic import ToolLogicMixin
from .traveling_merchant_logic import TravelingMerchantLogicMixin
from .wallet_logic import WalletLogicMixin
from .walnut_logic import WalnutLogicMixin
from ..content.game_content import StardewContent
from ..data.craftable_data import all_crafting_recipes
from ..data.museum_data import all_museum_items
@@ -50,16 +50,14 @@ from ..data.recipe_data import all_cooking_recipes
from ..mods.logic.magic_logic import MagicLogicMixin
from ..mods.logic.mod_logic import ModLogicMixin
from ..mods.mod_data import ModNames
from ..options import SpecialOrderLocations, ExcludeGingerIsland, FestivalLocations, StardewValleyOptions, Walnutsanity
from ..options import ExcludeGingerIsland, FestivalLocations, StardewValleyOptions
from ..stardew_rule import False_, True_, StardewRule
from ..strings.animal_names import Animal
from ..strings.animal_product_names import AnimalProduct
from ..strings.ap_names.ap_option_names import OptionName
from ..strings.ap_names.community_upgrade_names import CommunityUpgrade
from ..strings.ap_names.event_names import Event
from ..strings.artisan_good_names import ArtisanGood
from ..strings.building_names import Building
from ..strings.craftable_names import Consumable, Furniture, Ring, Fishing, Lighting, WildSeeds
from ..strings.craftable_names import Consumable, Ring, Fishing, Lighting, WildSeeds
from ..strings.crop_names import Fruit, Vegetable
from ..strings.currency_names import Currency
from ..strings.decoration_names import Decoration
@@ -96,7 +94,7 @@ class StardewLogic(ReceivedLogicMixin, HasLogicMixin, RegionLogicMixin, Travelin
CombatLogicMixin, MagicLogicMixin, MonsterLogicMixin, ToolLogicMixin, PetLogicMixin, QualityLogicMixin,
SkillLogicMixin, FarmingLogicMixin, BundleLogicMixin, FishingLogicMixin, MineLogicMixin, CookingLogicMixin, AbilityLogicMixin,
SpecialOrderLogicMixin, QuestLogicMixin, CraftingLogicMixin, ModLogicMixin, HarvestingLogicMixin, SourceLogicMixin,
RequirementLogicMixin, BookLogicMixin, GrindLogicMixin):
RequirementLogicMixin, BookLogicMixin, GrindLogicMixin, WalnutLogicMixin):
player: int
options: StardewValleyOptions
content: StardewContent
@@ -461,32 +459,6 @@ class StardewLogic(ReceivedLogicMixin, HasLogicMixin, RegionLogicMixin, Travelin
def can_smelt(self, item: str) -> StardewRule:
return self.has(Machine.furnace) & self.has(item)
@cached_property
def can_start_field_office(self) -> StardewRule:
field_office = self.region.can_reach(Region.field_office)
professor_snail = self.received("Open Professor Snail Cave")
return field_office & professor_snail
def can_complete_large_animal_collection(self) -> StardewRule:
fossils = self.has_all(Fossil.fossilized_leg, Fossil.fossilized_ribs, Fossil.fossilized_skull, Fossil.fossilized_spine, Fossil.fossilized_tail)
return self.can_start_field_office & fossils
def can_complete_snake_collection(self) -> StardewRule:
fossils = self.has_all(Fossil.snake_skull, Fossil.snake_vertebrae)
return self.can_start_field_office & fossils
def can_complete_frog_collection(self) -> StardewRule:
fossils = self.has_all(Fossil.mummified_frog)
return self.can_start_field_office & fossils
def can_complete_bat_collection(self) -> StardewRule:
fossils = self.has_all(Fossil.mummified_bat)
return self.can_start_field_office & fossils
def can_complete_field_office(self) -> StardewRule:
return self.can_complete_large_animal_collection() & self.can_complete_snake_collection() & \
self.can_complete_frog_collection() & self.can_complete_bat_collection()
def can_finish_grandpa_evaluation(self) -> StardewRule:
# https://stardewvalleywiki.com/Grandpa
rules_worth_a_point = [
@@ -566,86 +538,6 @@ class StardewLogic(ReceivedLogicMixin, HasLogicMixin, RegionLogicMixin, Travelin
return False_()
return self.region.can_reach(Region.island_trader)
def has_walnut(self, number: int) -> StardewRule:
if self.options.exclude_ginger_island == ExcludeGingerIsland.option_true:
return False_()
if number <= 0:
return True_()
if self.options.walnutsanity == Walnutsanity.preset_none:
return self.can_get_walnuts(number)
if self.options.walnutsanity == Walnutsanity.preset_all:
return self.has_received_walnuts(number)
puzzle_walnuts = 61
bush_walnuts = 25
dig_walnuts = 18
repeatable_walnuts = 33
total_walnuts = puzzle_walnuts + bush_walnuts + dig_walnuts + repeatable_walnuts
walnuts_to_receive = 0
walnuts_to_collect = number
if OptionName.walnutsanity_puzzles in self.options.walnutsanity:
puzzle_walnut_rate = puzzle_walnuts / total_walnuts
puzzle_walnuts_required = round(puzzle_walnut_rate * number)
walnuts_to_receive += puzzle_walnuts_required
walnuts_to_collect -= puzzle_walnuts_required
if OptionName.walnutsanity_bushes in self.options.walnutsanity:
bush_walnuts_rate = bush_walnuts / total_walnuts
bush_walnuts_required = round(bush_walnuts_rate * number)
walnuts_to_receive += bush_walnuts_required
walnuts_to_collect -= bush_walnuts_required
if OptionName.walnutsanity_dig_spots in self.options.walnutsanity:
dig_walnuts_rate = dig_walnuts / total_walnuts
dig_walnuts_required = round(dig_walnuts_rate * number)
walnuts_to_receive += dig_walnuts_required
walnuts_to_collect -= dig_walnuts_required
if OptionName.walnutsanity_repeatables in self.options.walnutsanity:
repeatable_walnuts_rate = repeatable_walnuts / total_walnuts
repeatable_walnuts_required = round(repeatable_walnuts_rate * number)
walnuts_to_receive += repeatable_walnuts_required
walnuts_to_collect -= repeatable_walnuts_required
return self.has_received_walnuts(walnuts_to_receive) & self.can_get_walnuts(walnuts_to_collect)
def has_received_walnuts(self, number: int) -> StardewRule:
return self.received(Event.received_walnuts, number)
def can_get_walnuts(self, number: int) -> StardewRule:
# https://stardewcommunitywiki.com/Golden_Walnut#Walnut_Locations
reach_south = self.region.can_reach(Region.island_south)
reach_north = self.region.can_reach(Region.island_north)
reach_west = self.region.can_reach(Region.island_west)
reach_hut = self.region.can_reach(Region.leo_hut)
reach_southeast = self.region.can_reach(Region.island_south_east)
reach_field_office = self.region.can_reach(Region.field_office)
reach_pirate_cove = self.region.can_reach(Region.pirate_cove)
reach_outside_areas = self.logic.and_(reach_south, reach_north, reach_west, reach_hut)
reach_volcano_regions = [self.region.can_reach(Region.volcano),
self.region.can_reach(Region.volcano_secret_beach),
self.region.can_reach(Region.volcano_floor_5),
self.region.can_reach(Region.volcano_floor_10)]
reach_volcano = self.logic.or_(*reach_volcano_regions)
reach_all_volcano = self.logic.and_(*reach_volcano_regions)
reach_walnut_regions = [reach_south, reach_north, reach_west, reach_volcano, reach_field_office]
reach_caves = self.logic.and_(self.region.can_reach(Region.qi_walnut_room), self.region.can_reach(Region.dig_site),
self.region.can_reach(Region.gourmand_frog_cave),
self.region.can_reach(Region.colored_crystals_cave),
self.region.can_reach(Region.shipwreck), self.combat.has_slingshot)
reach_entire_island = self.logic.and_(reach_outside_areas, reach_all_volcano,
reach_caves, reach_southeast, reach_field_office, reach_pirate_cove)
if number <= 5:
return self.logic.or_(reach_south, reach_north, reach_west, reach_volcano)
if number <= 10:
return self.count(2, *reach_walnut_regions)
if number <= 15:
return self.count(3, *reach_walnut_regions)
if number <= 20:
return self.logic.and_(*reach_walnut_regions)
if number <= 50:
return reach_entire_island
gems = (Mineral.amethyst, Mineral.aquamarine, Mineral.emerald, Mineral.ruby, Mineral.topaz)
return reach_entire_island & self.has(Fruit.banana) & self.has_all(*gems) & self.ability.can_mine_perfectly() & \
self.ability.can_fish_perfectly() & self.has(Furniture.flute_block) & self.has(Seed.melon) & self.has(Seed.wheat) & self.has(Seed.garlic) & \
self.can_complete_field_office()
def has_all_stardrops(self) -> StardewRule:
other_rules = []
number_of_stardrops_to_receive = 0

View File

@@ -41,7 +41,7 @@ class MuseumLogic(BaseLogic[Union[ReceivedLogicMixin, HasLogicMixin, TimeLogicMi
else:
geodes_rule = False_()
# monster_rule = self.can_farm_monster(item.monsters)
time_needed_to_grind = (20 - item.difficulty) / 2
time_needed_to_grind = int((20 - item.difficulty) // 2)
time_rule = self.logic.time.has_lived_months(time_needed_to_grind)
pan_rule = False_()
if item.item_name == Mineral.earth_crystal or item.item_name == Mineral.fire_quartz or item.item_name == Mineral.frozen_tear:

View File

@@ -9,8 +9,9 @@ from .season_logic import SeasonLogicMixin
from .skill_logic import SkillLogicMixin
from .time_logic import TimeLogicMixin
from .tool_logic import ToolLogicMixin
from .walnut_logic import WalnutLogicMixin
from ..data.game_item import Requirement
from ..data.requirement import ToolRequirement, BookRequirement, SkillRequirement, SeasonRequirement, YearRequirement
from ..data.requirement import ToolRequirement, BookRequirement, SkillRequirement, SeasonRequirement, YearRequirement, WalnutRequirement
class RequirementLogicMixin(BaseLogicMixin):
@@ -20,7 +21,7 @@ class RequirementLogicMixin(BaseLogicMixin):
class RequirementLogic(BaseLogic[Union[RequirementLogicMixin, HasLogicMixin, ReceivedLogicMixin, ToolLogicMixin, SkillLogicMixin, BookLogicMixin,
SeasonLogicMixin, TimeLogicMixin]]):
SeasonLogicMixin, TimeLogicMixin, WalnutLogicMixin]]):
def meet_all_requirements(self, requirements: Iterable[Requirement]):
if not requirements:
@@ -50,3 +51,7 @@ SeasonLogicMixin, TimeLogicMixin]]):
@meet_requirement.register
def _(self, requirement: YearRequirement):
return self.logic.time.has_year(requirement.year)
@meet_requirement.register
def _(self, requirement: WalnutRequirement):
return self.logic.walnut.has_walnut(requirement.amount)

View File

@@ -26,8 +26,10 @@ class TimeLogic(BaseLogic[Union[TimeLogicMixin, HasLogicMixin]]):
@cache_self1
def has_lived_months(self, number: int) -> StardewRule:
assert isinstance(number, int), "Can't have lived a fraction of a month. Use // instead of / when dividing."
if number <= 0:
return self.logic.true_
number = min(number, MAX_MONTHS)
return HasProgressionPercent(self.player, number * MONTH_COEFFICIENT)

View File

@@ -0,0 +1,135 @@
from functools import cached_property
from typing import Union
from .ability_logic import AbilityLogicMixin
from .base_logic import BaseLogic, BaseLogicMixin
from .combat_logic import CombatLogicMixin
from .has_logic import HasLogicMixin
from .received_logic import ReceivedLogicMixin
from .region_logic import RegionLogicMixin
from ..strings.ap_names.event_names import Event
from ..options import ExcludeGingerIsland, Walnutsanity
from ..stardew_rule import StardewRule, False_, True_
from ..strings.ap_names.ap_option_names import OptionName
from ..strings.craftable_names import Furniture
from ..strings.crop_names import Fruit
from ..strings.metal_names import Mineral, Fossil
from ..strings.region_names import Region
from ..strings.seed_names import Seed
class WalnutLogicMixin(BaseLogicMixin):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.walnut = WalnutLogic(*args, **kwargs)
class WalnutLogic(BaseLogic[Union[WalnutLogicMixin, ReceivedLogicMixin, HasLogicMixin, RegionLogicMixin, CombatLogicMixin,
AbilityLogicMixin]]):
def has_walnut(self, number: int) -> StardewRule:
if self.options.exclude_ginger_island == ExcludeGingerIsland.option_true:
return False_()
if number <= 0:
return True_()
if self.options.walnutsanity == Walnutsanity.preset_none:
return self.can_get_walnuts(number)
if self.options.walnutsanity == Walnutsanity.preset_all:
return self.has_received_walnuts(number)
puzzle_walnuts = 61
bush_walnuts = 25
dig_walnuts = 18
repeatable_walnuts = 33
total_walnuts = puzzle_walnuts + bush_walnuts + dig_walnuts + repeatable_walnuts
walnuts_to_receive = 0
walnuts_to_collect = number
if OptionName.walnutsanity_puzzles in self.options.walnutsanity:
puzzle_walnut_rate = puzzle_walnuts / total_walnuts
puzzle_walnuts_required = round(puzzle_walnut_rate * number)
walnuts_to_receive += puzzle_walnuts_required
walnuts_to_collect -= puzzle_walnuts_required
if OptionName.walnutsanity_bushes in self.options.walnutsanity:
bush_walnuts_rate = bush_walnuts / total_walnuts
bush_walnuts_required = round(bush_walnuts_rate * number)
walnuts_to_receive += bush_walnuts_required
walnuts_to_collect -= bush_walnuts_required
if OptionName.walnutsanity_dig_spots in self.options.walnutsanity:
dig_walnuts_rate = dig_walnuts / total_walnuts
dig_walnuts_required = round(dig_walnuts_rate * number)
walnuts_to_receive += dig_walnuts_required
walnuts_to_collect -= dig_walnuts_required
if OptionName.walnutsanity_repeatables in self.options.walnutsanity:
repeatable_walnuts_rate = repeatable_walnuts / total_walnuts
repeatable_walnuts_required = round(repeatable_walnuts_rate * number)
walnuts_to_receive += repeatable_walnuts_required
walnuts_to_collect -= repeatable_walnuts_required
return self.has_received_walnuts(walnuts_to_receive) & self.can_get_walnuts(walnuts_to_collect)
def has_received_walnuts(self, number: int) -> StardewRule:
return self.logic.received(Event.received_walnuts, number)
def can_get_walnuts(self, number: int) -> StardewRule:
# https://stardewcommunitywiki.com/Golden_Walnut#Walnut_Locations
reach_south = self.logic.region.can_reach(Region.island_south)
reach_north = self.logic.region.can_reach(Region.island_north)
reach_west = self.logic.region.can_reach(Region.island_west)
reach_hut = self.logic.region.can_reach(Region.leo_hut)
reach_southeast = self.logic.region.can_reach(Region.island_south_east)
reach_field_office = self.logic.region.can_reach(Region.field_office)
reach_pirate_cove = self.logic.region.can_reach(Region.pirate_cove)
reach_outside_areas = self.logic.and_(reach_south, reach_north, reach_west, reach_hut)
reach_volcano_regions = [self.logic.region.can_reach(Region.volcano),
self.logic.region.can_reach(Region.volcano_secret_beach),
self.logic.region.can_reach(Region.volcano_floor_5),
self.logic.region.can_reach(Region.volcano_floor_10)]
reach_volcano = self.logic.or_(*reach_volcano_regions)
reach_all_volcano = self.logic.and_(*reach_volcano_regions)
reach_walnut_regions = [reach_south, reach_north, reach_west, reach_volcano, reach_field_office]
reach_caves = self.logic.and_(self.logic.region.can_reach(Region.qi_walnut_room), self.logic.region.can_reach(Region.dig_site),
self.logic.region.can_reach(Region.gourmand_frog_cave),
self.logic.region.can_reach(Region.colored_crystals_cave),
self.logic.region.can_reach(Region.shipwreck), self.logic.combat.has_slingshot)
reach_entire_island = self.logic.and_(reach_outside_areas, reach_all_volcano,
reach_caves, reach_southeast, reach_field_office, reach_pirate_cove)
if number <= 5:
return self.logic.or_(reach_south, reach_north, reach_west, reach_volcano)
if number <= 10:
return self.logic.count(2, *reach_walnut_regions)
if number <= 15:
return self.logic.count(3, *reach_walnut_regions)
if number <= 20:
return self.logic.and_(*reach_walnut_regions)
if number <= 50:
return reach_entire_island
gems = (Mineral.amethyst, Mineral.aquamarine, Mineral.emerald, Mineral.ruby, Mineral.topaz)
return reach_entire_island & self.logic.has(Fruit.banana) & self.logic.has_all(*gems) & \
self.logic.ability.can_mine_perfectly() & self.logic.ability.can_fish_perfectly() & \
self.logic.has(Furniture.flute_block) & self.logic.has(Seed.melon) & self.logic.has(Seed.wheat) & \
self.logic.has(Seed.garlic) & self.can_complete_field_office()
@cached_property
def can_start_field_office(self) -> StardewRule:
field_office = self.logic.region.can_reach(Region.field_office)
professor_snail = self.logic.received("Open Professor Snail Cave")
return field_office & professor_snail
def can_complete_large_animal_collection(self) -> StardewRule:
fossils = self.logic.has_all(Fossil.fossilized_leg, Fossil.fossilized_ribs, Fossil.fossilized_skull, Fossil.fossilized_spine, Fossil.fossilized_tail)
return self.can_start_field_office & fossils
def can_complete_snake_collection(self) -> StardewRule:
fossils = self.logic.has_all(Fossil.snake_skull, Fossil.snake_vertebrae)
return self.can_start_field_office & fossils
def can_complete_frog_collection(self) -> StardewRule:
fossils = self.logic.has_all(Fossil.mummified_frog)
return self.can_start_field_office & fossils
def can_complete_bat_collection(self) -> StardewRule:
fossils = self.logic.has_all(Fossil.mummified_bat)
return self.can_start_field_office & fossils
def can_complete_field_office(self) -> StardewRule:
return self.can_complete_large_animal_collection() & self.can_complete_snake_collection() & \
self.can_complete_frog_collection() & self.can_complete_bat_collection()

View File

@@ -375,7 +375,7 @@ def set_ginger_island_rules(logic: StardewLogic, multiworld, player, world_optio
MultiWorldRules.add_rule(multiworld.get_location("Open Professor Snail Cave", player),
logic.has(Bomb.cherry_bomb))
MultiWorldRules.add_rule(multiworld.get_location("Complete Island Field Office", player),
logic.can_complete_field_office())
logic.walnut.can_complete_field_office())
set_walnut_rules(logic, multiworld, player, world_options)
@@ -432,10 +432,10 @@ def set_island_entrances_rules(logic: StardewLogic, multiworld, player, world_op
def set_island_parrot_rules(logic: StardewLogic, multiworld, player):
# Logic rules require more walnuts than in reality, to allow the player to spend them "wrong"
has_walnut = logic.has_walnut(5)
has_5_walnut = logic.has_walnut(15)
has_10_walnut = logic.has_walnut(40)
has_20_walnut = logic.has_walnut(60)
has_walnut = logic.walnut.has_walnut(5)
has_5_walnut = logic.walnut.has_walnut(15)
has_10_walnut = logic.walnut.has_walnut(40)
has_20_walnut = logic.walnut.has_walnut(60)
MultiWorldRules.add_rule(multiworld.get_location("Leo's Parrot", player),
has_walnut)
MultiWorldRules.add_rule(multiworld.get_location("Island West Turtle", player),
@@ -471,7 +471,7 @@ def set_walnut_rules(logic: StardewLogic, multiworld, player, world_options: Sta
set_walnut_repeatable_rules(logic, multiworld, player, world_options)
def set_walnut_puzzle_rules(logic, multiworld, player, world_options):
def set_walnut_puzzle_rules(logic: StardewLogic, multiworld, player, world_options):
if OptionName.walnutsanity_puzzles not in world_options.walnutsanity:
return
@@ -482,15 +482,17 @@ def set_walnut_puzzle_rules(logic, multiworld, player, world_options):
logic.has(Mineral.emerald) & logic.has(Mineral.ruby) & logic.has(Mineral.topaz) &
logic.region.can_reach_all((Region.island_north, Region.island_west, Region.island_east, Region.island_south)))
MultiWorldRules.add_rule(multiworld.get_location("Gourmand Frog Melon", player), logic.has(Fruit.melon) & logic.region.can_reach(Region.island_west))
MultiWorldRules.add_rule(multiworld.get_location("Gourmand Frog Wheat", player), logic.has(Vegetable.wheat) & logic.region.can_reach(Region.island_west))
MultiWorldRules.add_rule(multiworld.get_location("Gourmand Frog Garlic", player), logic.has(Vegetable.garlic) & logic.region.can_reach(Region.island_west))
MultiWorldRules.add_rule(multiworld.get_location("Gourmand Frog Wheat", player), logic.has(Vegetable.wheat) &
logic.region.can_reach(Region.island_west) & logic.region.can_reach_location("Gourmand Frog Melon"))
MultiWorldRules.add_rule(multiworld.get_location("Gourmand Frog Garlic", player), logic.has(Vegetable.garlic) &
logic.region.can_reach(Region.island_west) & logic.region.can_reach_location("Gourmand Frog Wheat"))
MultiWorldRules.add_rule(multiworld.get_location("Whack A Mole", player), logic.tool.has_tool(Tool.watering_can, ToolMaterial.iridium))
MultiWorldRules.add_rule(multiworld.get_location("Complete Large Animal Collection", player), logic.can_complete_large_animal_collection())
MultiWorldRules.add_rule(multiworld.get_location("Complete Snake Collection", player), logic.can_complete_snake_collection())
MultiWorldRules.add_rule(multiworld.get_location("Complete Mummified Frog Collection", player), logic.can_complete_frog_collection())
MultiWorldRules.add_rule(multiworld.get_location("Complete Mummified Bat Collection", player), logic.can_complete_bat_collection())
MultiWorldRules.add_rule(multiworld.get_location("Purple Flowers Island Survey", player), logic.can_start_field_office)
MultiWorldRules.add_rule(multiworld.get_location("Purple Starfish Island Survey", player), logic.can_start_field_office)
MultiWorldRules.add_rule(multiworld.get_location("Complete Large Animal Collection", player), logic.walnut.can_complete_large_animal_collection())
MultiWorldRules.add_rule(multiworld.get_location("Complete Snake Collection", player), logic.walnut.can_complete_snake_collection())
MultiWorldRules.add_rule(multiworld.get_location("Complete Mummified Frog Collection", player), logic.walnut.can_complete_frog_collection())
MultiWorldRules.add_rule(multiworld.get_location("Complete Mummified Bat Collection", player), logic.walnut.can_complete_bat_collection())
MultiWorldRules.add_rule(multiworld.get_location("Purple Flowers Island Survey", player), logic.walnut.can_start_field_office)
MultiWorldRules.add_rule(multiworld.get_location("Purple Starfish Island Survey", player), logic.walnut.can_start_field_office)
MultiWorldRules.add_rule(multiworld.get_location("Protruding Tree Walnut", player), logic.combat.has_slingshot)
MultiWorldRules.add_rule(multiworld.get_location("Starfish Tide Pool", player), logic.tool.has_fishing_rod(1))
MultiWorldRules.add_rule(multiworld.get_location("Mermaid Song", player), logic.has(Furniture.flute_block))

View File

@@ -431,7 +431,7 @@ class Count(BaseStardewRule):
return len(self.rules)
def __repr__(self):
return f"Received {self.count} {repr(self.rules)}"
return f"Received {self.count} [{', '.join(f'{value}x {repr(rule)}' for rule, value in self.counter.items())}]"
@dataclass(frozen=True)

View File

@@ -34,7 +34,7 @@ class RuleExplanation:
if not self.sub_rules:
return self.summary(depth)
return self.summary(depth) + "\n" + "\n".join(RuleExplanation.__str__(i, depth + 1)
return self.summary(depth) + "\n" + "\n".join(i.__str__(depth + 1)
if i.result is not self.expected else i.summary(depth + 1)
for i in sorted(self.explained_sub_rules, key=lambda x: x.result))
@@ -42,7 +42,7 @@ class RuleExplanation:
if not self.sub_rules:
return self.summary(depth)
return self.summary(depth) + "\n" + "\n".join(RuleExplanation.__repr__(i, depth + 1)
return self.summary(depth) + "\n" + "\n".join(i.__repr__(depth + 1)
for i in sorted(self.explained_sub_rules, key=lambda x: x.result))
@cached_property
@@ -61,6 +61,33 @@ class RuleExplanation:
return [_explain(i, self.state, self.expected, self.explored_rules_key) for i in self.sub_rules]
@dataclass
class CountSubRuleExplanation(RuleExplanation):
count: int = 1
@staticmethod
def from_explanation(expl: RuleExplanation, count: int) -> CountSubRuleExplanation:
return CountSubRuleExplanation(expl.rule, expl.state, expl.expected, expl.sub_rules, expl.explored_rules_key, expl.current_rule_explored, count)
def summary(self, depth=0) -> str:
summary = " " * depth + f"{self.count}x {str(self.rule)} -> {self.result}"
if self.current_rule_explored:
summary += " [Already explained]"
return summary
@dataclass
class CountExplanation(RuleExplanation):
rule: Count
@cached_property
def explained_sub_rules(self) -> List[RuleExplanation]:
return [
CountSubRuleExplanation.from_explanation(_explain(rule, self.state, self.expected, self.explored_rules_key), count)
for rule, count in self.rule.counter.items()
]
def explain(rule: CollectionRule, state: CollectionState, expected: bool = True) -> RuleExplanation:
if isinstance(rule, StardewRule):
return _explain(rule, state, expected, explored_spots=set())
@@ -80,7 +107,7 @@ def _(rule: AggregatingStardewRule, state: CollectionState, expected: bool, expl
@_explain.register
def _(rule: Count, state: CollectionState, expected: bool, explored_spots: Set[Tuple[str, str]]) -> RuleExplanation:
return RuleExplanation(rule, state, expected, rule.rules, explored_rules_key=explored_spots)
return CountExplanation(rule, state, expected, rule.rules, explored_rules_key=explored_spots)
@_explain.register

View File

@@ -122,4 +122,4 @@ class HasProgressionPercent(CombinableStardewRule):
return self, self(state)
def __repr__(self):
return f"Received {self.percent}% progression items."
return f"Received {self.percent}% progression items"

View File

@@ -1,4 +1,4 @@
from typing import Dict, List, Any, Tuple, TypedDict
from typing import Dict, List, Any, Tuple, TypedDict, ClassVar, Union
from logging import warning
from BaseClasses import Region, Location, Item, Tutorial, ItemClassification, MultiWorld
from .items import item_name_to_id, item_table, item_name_groups, fool_tiers, filler_items, slot_data_item_names
@@ -13,6 +13,14 @@ from .options import (TunicOptions, EntranceRando, tunic_option_groups, tunic_op
from worlds.AutoWorld import WebWorld, World
from Options import PlandoConnection
from decimal import Decimal, ROUND_HALF_UP
from settings import Group, Bool
class TunicSettings(Group):
class DisableLocalSpoiler(Bool):
"""Disallows the TUNIC client from creating a local spoiler log."""
disable_local_spoiler: Union[DisableLocalSpoiler, bool] = False
class TunicWeb(WebWorld):
@@ -60,6 +68,7 @@ class TunicWorld(World):
options: TunicOptions
options_dataclass = TunicOptions
settings: ClassVar[TunicSettings]
item_name_groups = item_name_groups
location_name_groups = location_name_groups
@@ -392,7 +401,8 @@ class TunicWorld(World):
"Hexagon Quest Holy Cross": self.ability_unlocks["Pages 42-43 (Holy Cross)"],
"Hexagon Quest Icebolt": self.ability_unlocks["Pages 52-53 (Icebolt)"],
"Hexagon Quest Goal": self.options.hexagon_goal.value,
"Entrance Rando": self.tunic_portal_pairs
"Entrance Rando": self.tunic_portal_pairs,
"disable_local_spoiler": int(self.settings.disable_local_spoiler or self.multiworld.is_race),
}
for tunic_item in filter(lambda item: item.location is not None and item.code is not None, self.slot_data_items):

View File

@@ -208,15 +208,15 @@ location_table: Dict[str, TunicLocationData] = {
"Monastery - Monastery Chest": TunicLocationData("Monastery", "Monastery Back"),
"Quarry - [Back Entrance] Bushes Holy Cross": TunicLocationData("Quarry Back", "Quarry Back", location_group="Holy Cross"),
"Quarry - [Back Entrance] Chest": TunicLocationData("Quarry Back", "Quarry Back"),
"Quarry - [Central] Near Shortcut Ladder": TunicLocationData("Quarry", "Quarry"),
"Quarry - [Central] Near Shortcut Ladder": TunicLocationData("Quarry Back", "Quarry Back"),
"Quarry - [East] Near Telescope": TunicLocationData("Quarry", "Quarry"),
"Quarry - [East] Upper Floor": TunicLocationData("Quarry", "Quarry"),
"Quarry - [Central] Below Entry Walkway": TunicLocationData("Quarry", "Quarry"),
"Quarry - [Central] Below Entry Walkway": TunicLocationData("Quarry Back", "Quarry Back"),
"Quarry - [East] Obscured Near Winding Staircase": TunicLocationData("Quarry", "Quarry"),
"Quarry - [East] Obscured Beneath Scaffolding": TunicLocationData("Quarry", "Quarry"),
"Quarry - [East] Obscured Near Telescope": TunicLocationData("Quarry", "Quarry"),
"Quarry - [Back Entrance] Obscured Behind Wall": TunicLocationData("Quarry Back", "Quarry Back"),
"Quarry - [Central] Obscured Below Entry Walkway": TunicLocationData("Quarry", "Quarry"),
"Quarry - [Central] Obscured Below Entry Walkway": TunicLocationData("Quarry Back", "Quarry Back"),
"Quarry - [Central] Top Floor Overhang": TunicLocationData("Quarry", "Quarry"),
"Quarry - [East] Near Bridge": TunicLocationData("Quarry", "Quarry"),
"Quarry - [Central] Above Ladder": TunicLocationData("Quarry", "Quarry Monastery Entry"),

View File

@@ -1,13 +1,13 @@
from typing import Dict, Set
from typing import Dict, List
booster_contents: Dict[str, Set[str]] = {
"LEGEND OF B.E.W.D.": {
booster_contents: Dict[str, List[str]] = {
"LEGEND OF B.E.W.D.": [
"Exodia",
"Dark Magician",
"Polymerization",
"Skull Servant"
},
"METAL RAIDERS": {
],
"METAL RAIDERS": [
"Petit Moth",
"Cocoon of Evolution",
"Time Wizard",
@@ -30,8 +30,8 @@ booster_contents: Dict[str, Set[str]] = {
"Solemn Judgment",
"Dream Clown",
"Heavy Storm"
},
"PHARAOH'S SERVANT": {
],
"PHARAOH'S SERVANT": [
"Beast of Talwar",
"Jinzo",
"Gearfried the Iron Knight",
@@ -43,8 +43,8 @@ booster_contents: Dict[str, Set[str]] = {
"The Shallow Grave",
"Nobleman of Crossout",
"Magic Drain"
},
"PHARAONIC GUARDIAN": {
],
"PHARAONIC GUARDIAN": [
"Don Zaloog",
"Reasoning",
"Dark Snake Syndrome",
@@ -71,8 +71,8 @@ booster_contents: Dict[str, Set[str]] = {
"Book of Taiyou",
"Dust Tornado",
"Raigeki Break"
},
"SPELL RULER": {
],
"SPELL RULER": [
"Ritual",
"Messenger of Peace",
"Megamorph",
@@ -94,8 +94,8 @@ booster_contents: Dict[str, Set[str]] = {
"Senju of the Thousand Hands",
"Sonic Bird",
"Mystical Space Typhoon"
},
"LABYRINTH OF NIGHTMARE": {
],
"LABYRINTH OF NIGHTMARE": [
"Destiny Board",
"Spirit Message 'I'",
"Spirit Message 'N'",
@@ -119,8 +119,8 @@ booster_contents: Dict[str, Set[str]] = {
"United We Stand",
"Earthbound Spirit",
"The Masked Beast"
},
"LEGACY OF DARKNESS": {
],
"LEGACY OF DARKNESS": [
"Last Turn",
"Yata-Garasu",
"Opticlops",
@@ -143,8 +143,8 @@ booster_contents: Dict[str, Set[str]] = {
"Maharaghi",
"Susa Soldier",
"Emergency Provisions",
},
"MAGICIAN'S FORCE": {
],
"MAGICIAN'S FORCE": [
"Huge Revolution",
"Oppressed People",
"United Resistance",
@@ -185,8 +185,8 @@ booster_contents: Dict[str, Set[str]] = {
"Royal Magical Library",
"Spell Shield Type-8",
"Tribute Doll",
},
"DARK CRISIS": {
],
"DARK CRISIS": [
"Final Countdown",
"Ojama Green",
"Dark Scorpion Combination",
@@ -213,8 +213,8 @@ booster_contents: Dict[str, Set[str]] = {
"Spell Reproduction",
"Contract with the Abyss",
"Dark Master - Zorc"
},
"INVASION OF CHAOS": {
],
"INVASION OF CHAOS": [
"Ojama Delta Hurricane",
"Ojama Yellow",
"Ojama Black",
@@ -241,8 +241,8 @@ booster_contents: Dict[str, Set[str]] = {
"Cursed Seal of the Forbidden Spell",
"Stray Lambs",
"Manju of the Ten Thousand Hands"
},
"ANCIENT SANCTUARY": {
],
"ANCIENT SANCTUARY": [
"Monster Gate",
"Wall of Revealing Light",
"Mystik Wok",
@@ -255,8 +255,8 @@ booster_contents: Dict[str, Set[str]] = {
"King of the Swamp",
"Enemy Controller",
"Enchanting Fitting Room"
},
"SOUL OF THE DUELIST": {
],
"SOUL OF THE DUELIST": [
"Ninja Grandmaster Sasuke",
"Mystic Swordsman LV2",
"Mystic Swordsman LV4",
@@ -272,8 +272,8 @@ booster_contents: Dict[str, Set[str]] = {
"Level Up!",
"Howling Insect",
"Mobius the Frost Monarch"
},
"RISE OF DESTINY": {
],
"RISE OF DESTINY": [
"Homunculus the Alchemic Being",
"Thestalos the Firestorm Monarch",
"Roc from the Valley of Haze",
@@ -283,8 +283,8 @@ booster_contents: Dict[str, Set[str]] = {
"Ultimate Insect Lv3",
"Divine Wrath",
"Serial Spell"
},
"FLAMING ETERNITY": {
],
"FLAMING ETERNITY": [
"Insect Knight",
"Chiron the Mage",
"Granmarg the Rock Monarch",
@@ -297,8 +297,8 @@ booster_contents: Dict[str, Set[str]] = {
"Golem Sentry",
"Rescue Cat",
"Blade Rabbit"
},
"THE LOST MILLENIUM": {
],
"THE LOST MILLENIUM": [
"Ritual",
"Megarock Dragon",
"D.D. Survivor",
@@ -311,8 +311,8 @@ booster_contents: Dict[str, Set[str]] = {
"Elemental Hero Thunder Giant",
"Aussa the Earth Charmer",
"Brain Control"
},
"CYBERNETIC REVOLUTION": {
],
"CYBERNETIC REVOLUTION": [
"Power Bond",
"Cyber Dragon",
"Cyber Twin Dragon",
@@ -322,8 +322,8 @@ booster_contents: Dict[str, Set[str]] = {
"Miracle Fusion",
"Elemental Hero Bubbleman",
"Jerry Beans Man"
},
"ELEMENTAL ENERGY": {
],
"ELEMENTAL ENERGY": [
"V-Tiger Jet",
"W-Wing Catapult",
"VW-Tiger Catapult",
@@ -344,8 +344,8 @@ booster_contents: Dict[str, Set[str]] = {
"Elemental Hero Bladedge",
"Pot of Avarice",
"B.E.S. Tetran"
},
"SHADOW OF INFINITY": {
],
"SHADOW OF INFINITY": [
"Hamon, Lord of Striking Thunder",
"Raviel, Lord of Phantasms",
"Uria, Lord of Searing Flames",
@@ -357,8 +357,8 @@ booster_contents: Dict[str, Set[str]] = {
"Gokipon",
"Demise, King of Armageddon",
"Anteatereatingant"
},
"GAME GIFT COLLECTION": {
],
"GAME GIFT COLLECTION": [
"Ritual",
"Valkyrion the Magna Warrior",
"Alpha the Magnet Warrior",
@@ -383,8 +383,8 @@ booster_contents: Dict[str, Set[str]] = {
"Card Destruction",
"Dark Magic Ritual",
"Calamity of the Wicked"
},
"Special Gift Collection": {
],
"Special Gift Collection": [
"Gate Guardian",
"Scapegoat",
"Gil Garth",
@@ -398,8 +398,8 @@ booster_contents: Dict[str, Set[str]] = {
"Curse of Vampire",
"Elemental Hero Flame Wingman",
"Magician of Black Chaos"
},
"Fairy Collection": {
],
"Fairy Collection": [
"Silpheed",
"Dunames Dark Witch",
"Hysteric Fairy",
@@ -416,8 +416,8 @@ booster_contents: Dict[str, Set[str]] = {
"Asura Priest",
"Manju of the Ten Thousand Hands",
"Senju of the Thousand Hands"
},
"Dragon Collection": {
],
"Dragon Collection": [
"Victory D.",
"Chaos Emperor Dragon - Envoy of the End",
"Kaiser Glider",
@@ -434,16 +434,16 @@ booster_contents: Dict[str, Set[str]] = {
"Troop Dragon",
"Horus the Black Flame Dragon LV4",
"Pitch-Dark Dragon"
},
"Warrior Collection A": {
],
"Warrior Collection A": [
"Gate Guardian",
"Gearfried the Iron Knight",
"Dimensional Warrior",
"Command Knight",
"The Last Warrior from Another Planet",
"Dream Clown"
},
"Warrior Collection B": {
],
"Warrior Collection B": [
"Don Zaloog",
"Dark Scorpion - Chick the Yellow",
"Dark Scorpion - Meanae the Thorn",
@@ -467,8 +467,8 @@ booster_contents: Dict[str, Set[str]] = {
"Blade Knight",
"Marauding Captain",
"Toon Goblin Attack Force"
},
"Fiend Collection A": {
],
"Fiend Collection A": [
"Sangan",
"Castle of Dark Illusions",
"Barox",
@@ -480,8 +480,8 @@ booster_contents: Dict[str, Set[str]] = {
"Spear Cretin",
"Versago the Destroyer",
"Toon Summoned Skull"
},
"Fiend Collection B": {
],
"Fiend Collection B": [
"Raviel, Lord of Phantasms",
"Yata-Garasu",
"Helpoemer",
@@ -505,15 +505,15 @@ booster_contents: Dict[str, Set[str]] = {
"Jowls of Dark Demise",
"D. D. Trainer",
"Earthbound Spirit"
},
"Machine Collection A": {
],
"Machine Collection A": [
"Cyber-Stein",
"Mechanicalchaser",
"Jinzo",
"UFO Turtle",
"Cyber-Tech Alligator"
},
"Machine Collection B": {
],
"Machine Collection B": [
"X-Head Cannon",
"Y-Dragon Head",
"Z-Metal Tank",
@@ -531,8 +531,8 @@ booster_contents: Dict[str, Set[str]] = {
"Red Gadget",
"Yellow Gadget",
"B.E.S. Tetran"
},
"Spellcaster Collection A": {
],
"Spellcaster Collection A": [
"Exodia",
"Dark Sage",
"Dark Magician",
@@ -544,8 +544,8 @@ booster_contents: Dict[str, Set[str]] = {
"Injection Fairy Lily",
"Cosmo Queen",
"Magician of Black Chaos"
},
"Spellcaster Collection B": {
],
"Spellcaster Collection B": [
"Jowgen the Spiritualist",
"Tsukuyomi",
"Manticore of Darkness",
@@ -574,8 +574,8 @@ booster_contents: Dict[str, Set[str]] = {
"Royal Magical Library",
"Aussa the Earth Charmer",
},
"Zombie Collection": {
],
"Zombie Collection": [
"Skull Servant",
"Regenerating Mummy",
"Ryu Kokki",
@@ -590,8 +590,8 @@ booster_contents: Dict[str, Set[str]] = {
"Des Lacooda",
"Wandering Mummy",
"Royal Keeper"
},
"Special Monsters A": {
],
"Special Monsters A": [
"X-Head Cannon",
"Y-Dragon Head",
"Z-Metal Tank",
@@ -626,8 +626,8 @@ booster_contents: Dict[str, Set[str]] = {
"Fushi No Tori",
"Maharaghi",
"Susa Soldier"
},
"Special Monsters B": {
],
"Special Monsters B": [
"Polymerization",
"Mystic Swordsman LV2",
"Mystic Swordsman LV4",
@@ -656,8 +656,8 @@ booster_contents: Dict[str, Set[str]] = {
"Level Up!",
"Ultimate Insect Lv3",
"Ultimate Insect Lv5"
},
"Reverse Collection": {
],
"Reverse Collection": [
"Magical Merchant",
"Castle of Dark Illusions",
"Magician of Faith",
@@ -675,8 +675,8 @@ booster_contents: Dict[str, Set[str]] = {
"Spear Cretin",
"Nobleman of Crossout",
"Aussa the Earth Charmer"
},
"LP Recovery Collection": {
],
"LP Recovery Collection": [
"Mystik Wok",
"Poison of the Old Man",
"Hysteric Fairy",
@@ -691,8 +691,8 @@ booster_contents: Dict[str, Set[str]] = {
"Elemental Hero Steam Healer",
"Fushi No Tori",
"Emergency Provisions"
},
"Special Summon Collection A": {
],
"Special Summon Collection A": [
"Perfectly Ultimate Great Moth",
"Dark Sage",
"Polymerization",
@@ -726,8 +726,8 @@ booster_contents: Dict[str, Set[str]] = {
"Morphing Jar #2",
"Spear Cretin",
"Dark Magic Curtain"
},
"Special Summon Collection B": {
],
"Special Summon Collection B": [
"Monster Gate",
"Chaos Emperor Dragon - Envoy of the End",
"Ojama Trio",
@@ -756,8 +756,8 @@ booster_contents: Dict[str, Set[str]] = {
"Tribute Doll",
"Enchanting Fitting Room",
"Stray Lambs"
},
"Special Summon Collection C": {
],
"Special Summon Collection C": [
"Hamon, Lord of Striking Thunder",
"Raviel, Lord of Phantasms",
"Uria, Lord of Searing Flames",
@@ -782,13 +782,13 @@ booster_contents: Dict[str, Set[str]] = {
"Ultimate Insect Lv5",
"Rescue Cat",
"Anteatereatingant"
},
"Equipment Collection": {
],
"Equipment Collection": [
"Megamorph",
"Cestus of Dagla",
"United We Stand"
},
"Continuous Spell/Trap A": {
],
"Continuous Spell/Trap A": [
"Destiny Board",
"Spirit Message 'I'",
"Spirit Message 'N'",
@@ -801,8 +801,8 @@ booster_contents: Dict[str, Set[str]] = {
"Solemn Wishes",
"Embodiment of Apophis",
"Toon World"
},
"Continuous Spell/Trap B": {
],
"Continuous Spell/Trap B": [
"Hamon, Lord of Striking Thunder",
"Uria, Lord of Searing Flames",
"Wave-Motion Cannon",
@@ -815,8 +815,8 @@ booster_contents: Dict[str, Set[str]] = {
"Skull Zoma",
"Pitch-Black Power Stone",
"Metal Reflect Slime"
},
"Quick/Counter Collection": {
],
"Quick/Counter Collection": [
"Mystik Wok",
"Poison of the Old Man",
"Scapegoat",
@@ -841,8 +841,8 @@ booster_contents: Dict[str, Set[str]] = {
"Book of Moon",
"Serial Spell",
"Mystical Space Typhoon"
},
"Direct Damage Collection": {
],
"Direct Damage Collection": [
"Hamon, Lord of Striking Thunder",
"Chaos Emperor Dragon - Envoy of the End",
"Dark Snake Syndrome",
@@ -868,8 +868,8 @@ booster_contents: Dict[str, Set[str]] = {
"Jowls of Dark Demise",
"Stealth Bird",
"Elemental Hero Bladedge",
},
"Direct Attack Collection": {
],
"Direct Attack Collection": [
"Victory D.",
"Dark Scorpion Combination",
"Spirit Reaper",
@@ -880,8 +880,8 @@ booster_contents: Dict[str, Set[str]] = {
"Toon Mermaid",
"Toon Summoned Skull",
"Toon Dark Magician Girl"
},
"Monster Destroy Collection": {
],
"Monster Destroy Collection": [
"Hamon, Lord of Striking Thunder",
"Inferno",
"Ninja Grandmaster Sasuke",
@@ -912,12 +912,12 @@ booster_contents: Dict[str, Set[str]] = {
"Offerings to the Doomed",
"Divine Wrath",
"Dream Clown"
},
],
}
def get_booster_locations(booster: str) -> Dict[str, str]:
return {
f"{booster} {i}": content
for i, content in enumerate(booster_contents[booster])
for i, content in enumerate(booster_contents[booster], 1)
}

View File

@@ -1,7 +1,7 @@
from typing import Dict, Set
from typing import Dict, List
structure_contents: Dict[str, Set] = {
"dragons_roar": {
structure_contents: Dict[str, List[str]] = {
"dragons_roar": [
"Luster Dragon",
"Armed Dragon LV3",
"Armed Dragon LV5",
@@ -14,9 +14,9 @@ structure_contents: Dict[str, Set] = {
"Stamping Destruction",
"Heavy Storm",
"Dust Tornado",
"Mystical Space Typhoon",
},
"zombie_madness": {
"Mystical Space Typhoon"
],
"zombie_madness": [
"Pyramid Turtle",
"Regenerating Mummy",
"Ryu Kokki",
@@ -26,9 +26,9 @@ structure_contents: Dict[str, Set] = {
"Reload",
"Heavy Storm",
"Dust Tornado",
"Mystical Space Typhoon",
},
"blazing_destruction": {
"Mystical Space Typhoon"
],
"blazing_destruction": [
"Inferno",
"Solar Flare Dragon",
"UFO Turtle",
@@ -38,9 +38,9 @@ structure_contents: Dict[str, Set] = {
"Level Limit - Area B",
"Heavy Storm",
"Dust Tornado",
"Mystical Space Typhoon",
},
"fury_from_the_deep": {
"Mystical Space Typhoon"
],
"fury_from_the_deep": [
"Mother Grizzly",
"Water Beaters",
"Gravity Bind",
@@ -48,9 +48,9 @@ structure_contents: Dict[str, Set] = {
"Mobius the Frost Monarch",
"Heavy Storm",
"Dust Tornado",
"Mystical Space Typhoon",
},
"warriors_triumph": {
"Mystical Space Typhoon"
],
"warriors_triumph": [
"Gearfried the Iron Knight",
"D.D. Warrior Lady",
"Marauding Captain",
@@ -60,9 +60,9 @@ structure_contents: Dict[str, Set] = {
"Reload",
"Heavy Storm",
"Dust Tornado",
"Mystical Space Typhoon",
},
"spellcasters_judgement": {
"Mystical Space Typhoon"
],
"spellcasters_judgement": [
"Dark Magician",
"Apprentice Magician",
"Breaker the Magical Warrior",
@@ -70,14 +70,18 @@ structure_contents: Dict[str, Set] = {
"Skilled Dark Magician",
"Tsukuyomi",
"Magical Dimension",
"Mage PowerSpell-Counter Cards",
"Mage Power",
"Spell-Counter Cards",
"Heavy Storm",
"Dust Tornado",
"Mystical Space Typhoon",
},
"none": {},
"Mystical Space Typhoon"
],
"none": [],
}
def get_deck_content_locations(deck: str) -> Dict[str, str]:
return {f"{deck} {i}": content for i, content in enumerate(structure_contents[deck])}
return {
f"{deck} {i}": content
for i, content in enumerate(structure_contents[deck], 1)
}