Merge remote-tracking branch 'Main/main' into rework_accessibility

This commit is contained in:
alwaysintreble
2023-02-26 21:25:59 -06:00
83 changed files with 12142 additions and 348 deletions

View File

@@ -1261,13 +1261,9 @@ class Spoiler():
self.multiworld = world
self.hashes = {}
self.entrances = OrderedDict()
self.medallions = {}
self.playthrough = {}
self.unreachables = set()
self.locations = {}
self.paths = {}
self.shops = []
self.bosses = OrderedDict()
def set_entrance(self, entrance: str, exit_: str, direction: str, player: int):
if self.multiworld.players == 1:
@@ -1277,126 +1273,6 @@ class Spoiler():
self.entrances[(entrance, direction, player)] = OrderedDict(
[('player', player), ('entrance', entrance), ('exit', exit_), ('direction', direction)])
def parse_data(self):
from worlds.alttp.SubClasses import LTTPRegionType
self.medallions = OrderedDict()
for player in self.multiworld.get_game_players("A Link to the Past"):
self.medallions[f'Misery Mire ({self.multiworld.get_player_name(player)})'] = \
self.multiworld.required_medallions[player][0]
self.medallions[f'Turtle Rock ({self.multiworld.get_player_name(player)})'] = \
self.multiworld.required_medallions[player][1]
self.locations = OrderedDict()
listed_locations = set()
lw_locations = []
dw_locations = []
cave_locations = []
for loc in self.multiworld.get_locations():
if loc.game == "A Link to the Past":
if loc not in listed_locations and loc.parent_region and \
loc.parent_region.type == LTTPRegionType.LightWorld and loc.show_in_spoiler:
lw_locations.append(loc)
elif loc not in listed_locations and loc.parent_region and \
loc.parent_region.type == LTTPRegionType.DarkWorld and loc.show_in_spoiler:
dw_locations.append(loc)
elif loc not in listed_locations and loc.parent_region and \
loc.parent_region.type == LTTPRegionType.Cave and loc.show_in_spoiler:
cave_locations.append(loc)
self.locations['Light World'] = OrderedDict(
[(str(location), str(location.item) if location.item is not None else 'Nothing') for location in
lw_locations])
listed_locations.update(lw_locations)
self.locations['Dark World'] = OrderedDict(
[(str(location), str(location.item) if location.item is not None else 'Nothing') for location in
dw_locations])
listed_locations.update(dw_locations)
self.locations['Caves'] = OrderedDict(
[(str(location), str(location.item) if location.item is not None else 'Nothing') for location in
cave_locations])
listed_locations.update(cave_locations)
for dungeon in self.multiworld.dungeons.values():
dungeon_locations = [loc for loc in self.multiworld.get_locations() if
loc not in listed_locations and loc.parent_region and loc.parent_region.dungeon == dungeon and loc.show_in_spoiler]
self.locations[str(dungeon)] = OrderedDict(
[(str(location), str(location.item) if location.item is not None else 'Nothing') for location in
dungeon_locations])
listed_locations.update(dungeon_locations)
other_locations = [loc for loc in self.multiworld.get_locations() if
loc not in listed_locations and loc.show_in_spoiler]
if other_locations:
self.locations['Other Locations'] = OrderedDict(
[(str(location), str(location.item) if location.item is not None else 'Nothing') for location in
other_locations])
listed_locations.update(other_locations)
self.shops = []
from worlds.alttp.Shops import ShopType, price_type_display_name, price_rate_display
for shop in self.multiworld.shops:
if not shop.custom:
continue
shopdata = {
'location': str(shop.region),
'type': 'Take Any' if shop.type == ShopType.TakeAny else 'Shop'
}
for index, item in enumerate(shop.inventory):
if item is None:
continue
my_price = item['price'] // price_rate_display.get(item['price_type'], 1)
shopdata['item_{}'.format(
index)] = f"{item['item']}{my_price} {price_type_display_name[item['price_type']]}"
if item['player'] > 0:
shopdata['item_{}'.format(index)] = shopdata['item_{}'.format(index)].replace('',
'(Player {}) — '.format(
item['player']))
if item['max'] == 0:
continue
shopdata['item_{}'.format(index)] += " x {}".format(item['max'])
if item['replacement'] is None:
continue
shopdata['item_{}'.format(
index)] += f", {item['replacement']} - {item['replacement_price']} {price_type_display_name[item['replacement_price_type']]}"
self.shops.append(shopdata)
for player in self.multiworld.get_game_players("A Link to the Past"):
self.bosses[str(player)] = OrderedDict()
self.bosses[str(player)]["Eastern Palace"] = self.multiworld.get_dungeon("Eastern Palace", player).boss.name
self.bosses[str(player)]["Desert Palace"] = self.multiworld.get_dungeon("Desert Palace", player).boss.name
self.bosses[str(player)]["Tower Of Hera"] = self.multiworld.get_dungeon("Tower of Hera", player).boss.name
self.bosses[str(player)]["Hyrule Castle"] = "Agahnim"
self.bosses[str(player)]["Palace Of Darkness"] = self.multiworld.get_dungeon("Palace of Darkness",
player).boss.name
self.bosses[str(player)]["Swamp Palace"] = self.multiworld.get_dungeon("Swamp Palace", player).boss.name
self.bosses[str(player)]["Skull Woods"] = self.multiworld.get_dungeon("Skull Woods", player).boss.name
self.bosses[str(player)]["Thieves Town"] = self.multiworld.get_dungeon("Thieves Town", player).boss.name
self.bosses[str(player)]["Ice Palace"] = self.multiworld.get_dungeon("Ice Palace", player).boss.name
self.bosses[str(player)]["Misery Mire"] = self.multiworld.get_dungeon("Misery Mire", player).boss.name
self.bosses[str(player)]["Turtle Rock"] = self.multiworld.get_dungeon("Turtle Rock", player).boss.name
if self.multiworld.mode[player] != 'inverted':
self.bosses[str(player)]["Ganons Tower Basement"] = \
self.multiworld.get_dungeon('Ganons Tower', player).bosses['bottom'].name
self.bosses[str(player)]["Ganons Tower Middle"] = self.multiworld.get_dungeon('Ganons Tower', player).bosses[
'middle'].name
self.bosses[str(player)]["Ganons Tower Top"] = self.multiworld.get_dungeon('Ganons Tower', player).bosses[
'top'].name
else:
self.bosses[str(player)]["Ganons Tower Basement"] = \
self.multiworld.get_dungeon('Inverted Ganons Tower', player).bosses['bottom'].name
self.bosses[str(player)]["Ganons Tower Middle"] = \
self.multiworld.get_dungeon('Inverted Ganons Tower', player).bosses['middle'].name
self.bosses[str(player)]["Ganons Tower Top"] = \
self.multiworld.get_dungeon('Inverted Ganons Tower', player).bosses['top'].name
self.bosses[str(player)]["Ganons Tower"] = "Agahnim 2"
self.bosses[str(player)]["Ganon"] = "Ganon"
def create_playthrough(self, create_paths: bool = True):
"""Destructive to the world while it is run, damage gets repaired afterwards."""
from itertools import chain
@@ -1548,30 +1424,7 @@ class Spoiler():
self.paths[str(multiworld.get_region('Inverted Big Bomb Shop', player))] = \
get_path(state, multiworld.get_region('Inverted Big Bomb Shop', player))
def to_json(self):
self.parse_data()
out = OrderedDict()
out['Entrances'] = list(self.entrances.values())
out.update(self.locations)
out['Special'] = self.medallions
if self.hashes:
out['Hashes'] = self.hashes
if self.shops:
out['Shops'] = self.shops
out['playthrough'] = self.playthrough
out['paths'] = self.paths
out['Bosses'] = self.bosses
return json.dumps(out)
def to_file(self, filename: str):
self.parse_data()
def bool_to_text(variable: Union[bool, str]) -> str:
if type(variable) == str:
return variable
return 'Yes' if variable else 'No'
def write_option(option_key: str, option_obj: type(Options.Option)):
res = getattr(self.multiworld, option_key)[player]
display_name = getattr(option_obj, "display_name", option_key)
@@ -1601,38 +1454,6 @@ class Spoiler():
write_option(f_option, option)
AutoWorld.call_single(self.multiworld, "write_spoiler_header", player, outfile)
if player in self.multiworld.get_game_players("A Link to the Past"):
outfile.write('%s%s\n' % ('Hash: ', self.hashes[player]))
outfile.write('Logic: %s\n' % self.multiworld.logic[player])
outfile.write('Dark Room Logic: %s\n' % self.multiworld.dark_room_logic[player])
outfile.write('Mode: %s\n' % self.multiworld.mode[player])
outfile.write('Goal: %s\n' % self.multiworld.goal[player])
if "triforce" in self.multiworld.goal[player]: # triforce hunt
outfile.write("Pieces available for Triforce: %s\n" %
self.multiworld.triforce_pieces_available[player])
outfile.write("Pieces required for Triforce: %s\n" %
self.multiworld.triforce_pieces_required[player])
outfile.write('Difficulty: %s\n' % self.multiworld.difficulty[player])
outfile.write('Item Functionality: %s\n' % self.multiworld.item_functionality[player])
outfile.write('Entrance Shuffle: %s\n' % self.multiworld.shuffle[player])
if self.multiworld.shuffle[player] != "vanilla":
outfile.write('Entrance Shuffle Seed %s\n' % self.multiworld.worlds[player].er_seed)
outfile.write('Shop inventory shuffle: %s\n' %
bool_to_text("i" in self.multiworld.shop_shuffle[player]))
outfile.write('Shop price shuffle: %s\n' %
bool_to_text("p" in self.multiworld.shop_shuffle[player]))
outfile.write('Shop upgrade shuffle: %s\n' %
bool_to_text("u" in self.multiworld.shop_shuffle[player]))
outfile.write('New Shop inventory: %s\n' %
bool_to_text("g" in self.multiworld.shop_shuffle[player] or
"f" in self.multiworld.shop_shuffle[player]))
outfile.write('Custom Potion Shop: %s\n' %
bool_to_text("w" in self.multiworld.shop_shuffle[player]))
outfile.write('Enemy health: %s\n' % self.multiworld.enemy_health[player])
outfile.write('Enemy damage: %s\n' % self.multiworld.enemy_damage[player])
outfile.write('Prize shuffle %s\n' %
self.multiworld.shuffle_prizes[player])
if self.entrances:
outfile.write('\n\nEntrances:\n\n')
outfile.write('\n'.join(['%s%s %s %s' % (f'{self.multiworld.get_player_name(entry["player"])}: '
@@ -1641,30 +1462,14 @@ class Spoiler():
'<=' if entry['direction'] == 'exit' else '=>',
entry['exit']) for entry in self.entrances.values()]))
if self.medallions:
outfile.write('\n\nMedallions:\n')
for dungeon, medallion in self.medallions.items():
outfile.write(f'\n{dungeon}: {medallion}')
AutoWorld.call_all(self.multiworld, "write_spoiler", outfile)
locations = [(str(location), str(location.item) if location.item is not None else "Nothing")
for location in self.multiworld.get_locations()]
outfile.write('\n\nLocations:\n\n')
outfile.write('\n'.join(
['%s: %s' % (location, item) for grouping in self.locations.values() for (location, item) in
grouping.items()]))
['%s: %s' % (location, item) for location, item in locations]))
if self.shops:
outfile.write('\n\nShops:\n\n')
outfile.write('\n'.join("{} [{}]\n {}".format(shop['location'], shop['type'], "\n ".join(
item for item in [shop.get('item_0', None), shop.get('item_1', None), shop.get('item_2', None)] if
item)) for shop in self.shops))
for player in self.multiworld.get_game_players("A Link to the Past"):
if self.multiworld.boss_shuffle[player] != 'none':
bossmap = self.bosses[str(player)] if self.multiworld.players > 1 else self.bosses
outfile.write(
f'\n\nBosses{(f" ({self.multiworld.get_player_name(player)})" if self.multiworld.players > 1 else "")}:\n')
outfile.write(' ' + '\n '.join([f'{x}: {y}' for x, y in bossmap.items()]))
outfile.write('\n\nPlaythrough:\n\n')
outfile.write('\n'.join(['%s: {\n%s\n}' % (sphere_nr, '\n'.join(
[' %s: %s' % (location, item) for (location, item) in sphere.items()] if sphere_nr != '0' else [

View File

@@ -151,6 +151,8 @@ components: Iterable[Component] = (
Component('ChecksFinder Client', 'ChecksFinderClient'),
# Starcraft 2
Component('Starcraft 2 Client', 'Starcraft2Client'),
# Wargroove
Component('Wargroove Client', 'WargrooveClient'),
# Zillion
Component('Zillion Client', 'ZillionClient',
file_identifier=SuffixIdentifier('.apzl')),

View File

@@ -310,6 +310,9 @@ def get_default_options() -> OptionsType:
"lufia2ac_options": {
"rom_file": "Lufia II - Rise of the Sinistrals (USA).sfc",
},
"wargroove_options": {
"root_directory": "C:/Program Files (x86)/Steam/steamapps/common/Wargroove"
}
}
return options

443
WargrooveClient.py Normal file
View File

@@ -0,0 +1,443 @@
from __future__ import annotations
import os
import sys
import asyncio
import random
import shutil
from typing import Tuple, List, Iterable, Dict
from worlds.wargroove import WargrooveWorld
from worlds.wargroove.Items import item_table, faction_table, CommanderData, ItemData
import ModuleUpdate
ModuleUpdate.update()
import Utils
import json
import logging
if __name__ == "__main__":
Utils.init_logging("WargrooveClient", exception_logger="Client")
from NetUtils import NetworkItem, ClientStatus
from CommonClient import gui_enabled, logger, get_base_parser, ClientCommandProcessor, \
CommonContext, server_loop
wg_logger = logging.getLogger("WG")
class WargrooveClientCommandProcessor(ClientCommandProcessor):
def _cmd_resync(self):
"""Manually trigger a resync."""
self.output(f"Syncing items.")
self.ctx.syncing = True
def _cmd_commander(self, *commander_name: Iterable[str]):
"""Set the current commander to the given commander."""
if commander_name:
self.ctx.set_commander(' '.join(commander_name))
else:
if self.ctx.can_choose_commander:
commanders = self.ctx.get_commanders()
wg_logger.info('Unlocked commanders: ' +
', '.join((commander.name for commander, unlocked in commanders if unlocked)))
wg_logger.info('Locked commanders: ' +
', '.join((commander.name for commander, unlocked in commanders if not unlocked)))
else:
wg_logger.error('Cannot set commanders in this game mode.')
class WargrooveContext(CommonContext):
command_processor: int = WargrooveClientCommandProcessor
game = "Wargroove"
items_handling = 0b111 # full remote
current_commander: CommanderData = faction_table["Starter"][0]
can_choose_commander: bool = False
commander_defense_boost_multiplier: int = 0
income_boost_multiplier: int = 0
starting_groove_multiplier: float
faction_item_ids = {
'Starter': 0,
'Cherrystone': 52025,
'Felheim': 52026,
'Floran': 52027,
'Heavensong': 52028,
'Requiem': 52029,
'Outlaw': 52030
}
buff_item_ids = {
'Income Boost': 52023,
'Commander Defense Boost': 52024,
}
def __init__(self, server_address, password):
super(WargrooveContext, self).__init__(server_address, password)
self.send_index: int = 0
self.syncing = False
self.awaiting_bridge = False
# self.game_communication_path: files go in this path to pass data between us and the actual game
if "appdata" in os.environ:
options = Utils.get_options()
root_directory = options["wargroove_options"]["root_directory"].replace("/", "\\")
data_directory = "lib\\worlds\\wargroove\\data\\"
dev_data_directory = "worlds\\wargroove\\data\\"
appdata_wargroove = os.path.expandvars("%APPDATA%\\Chucklefish\\Wargroove\\")
if not os.path.isfile(root_directory + "\\win64_bin\\wargroove64.exe"):
print_error_and_close("WargrooveClient couldn't find wargroove64.exe. "
"Unable to infer required game_communication_path")
self.game_communication_path = root_directory + "\\AP"
if not os.path.exists(self.game_communication_path):
os.makedirs(self.game_communication_path)
if not os.path.isdir(appdata_wargroove):
print_error_and_close("WargrooveClient couldn't find Wargoove in appdata!"
"Boot Wargroove and then close it to attempt to fix this error")
if not os.path.isdir(data_directory):
data_directory = dev_data_directory
if not os.path.isdir(data_directory):
print_error_and_close("WargrooveClient couldn't find Wargoove mod and save files in install!")
shutil.copytree(data_directory, appdata_wargroove, dirs_exist_ok=True)
else:
print_error_and_close("WargrooveClient couldn't detect system type. "
"Unable to infer required game_communication_path")
async def server_auth(self, password_requested: bool = False):
if password_requested and not self.password:
await super(WargrooveContext, self).server_auth(password_requested)
await self.get_username()
await self.send_connect()
async def connection_closed(self):
await super(WargrooveContext, self).connection_closed()
for root, dirs, files in os.walk(self.game_communication_path):
for file in files:
if file.find("obtain") <= -1:
os.remove(root + "/" + file)
@property
def endpoints(self):
if self.server:
return [self.server]
else:
return []
async def shutdown(self):
await super(WargrooveContext, self).shutdown()
for root, dirs, files in os.walk(self.game_communication_path):
for file in files:
if file.find("obtain") <= -1:
os.remove(root+"/"+file)
def on_package(self, cmd: str, args: dict):
if cmd in {"Connected"}:
filename = f"AP_settings.json"
with open(os.path.join(self.game_communication_path, filename), 'w') as f:
slot_data = args["slot_data"]
json.dump(args["slot_data"], f)
self.can_choose_commander = slot_data["can_choose_commander"]
print('can choose commander:', self.can_choose_commander)
self.starting_groove_multiplier = slot_data["starting_groove_multiplier"]
self.income_boost_multiplier = slot_data["income_boost"]
self.commander_defense_boost_multiplier = slot_data["commander_defense_boost"]
f.close()
for ss in self.checked_locations:
filename = f"send{ss}"
with open(os.path.join(self.game_communication_path, filename), 'w') as f:
f.close()
self.update_commander_data()
self.ui.update_tracker()
random.seed(self.seed_name + str(self.slot))
# Our indexes start at 1 and we have 24 levels
for i in range(1, 25):
filename = f"seed{i}"
with open(os.path.join(self.game_communication_path, filename), 'w') as f:
f.write(str(random.randint(0, 4294967295)))
f.close()
if cmd in {"RoomInfo"}:
self.seed_name = args["seed_name"]
if cmd in {"ReceivedItems"}:
received_ids = [item.item for item in self.items_received]
for network_item in self.items_received:
filename = f"AP_{str(network_item.item)}.item"
path = os.path.join(self.game_communication_path, filename)
# Newly-obtained items
if not os.path.isfile(path):
open(path, 'w').close()
# Announcing commander unlocks
item_name = self.item_names[network_item.item]
if item_name in faction_table.keys():
for commander in faction_table[item_name]:
logger.info(f"{commander.name} has been unlocked!")
with open(path, 'w') as f:
item_count = received_ids.count(network_item.item)
if self.buff_item_ids["Income Boost"] == network_item.item:
f.write(f"{item_count * self.income_boost_multiplier}")
elif self.buff_item_ids["Commander Defense Boost"] == network_item.item:
f.write(f"{item_count * self.commander_defense_boost_multiplier}")
else:
f.write(f"{item_count}")
f.close()
print_filename = f"AP_{str(network_item.item)}.item.print"
print_path = os.path.join(self.game_communication_path, print_filename)
if not os.path.isfile(print_path):
open(print_path, 'w').close()
with open(print_path, 'w') as f:
f.write("Received " +
self.item_names[network_item.item] +
" from " +
self.player_names[network_item.player])
f.close()
self.update_commander_data()
self.ui.update_tracker()
if cmd in {"RoomUpdate"}:
if "checked_locations" in args:
for ss in self.checked_locations:
filename = f"send{ss}"
with open(os.path.join(self.game_communication_path, filename), 'w') as f:
f.close()
def run_gui(self):
"""Import kivy UI system and start running it as self.ui_task."""
from kvui import GameManager, HoverBehavior, ServerToolTip
from kivy.uix.tabbedpanel import TabbedPanelItem
from kivy.lang import Builder
from kivy.uix.button import Button
from kivy.uix.togglebutton import ToggleButton
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.gridlayout import GridLayout
from kivy.uix.image import AsyncImage, Image
from kivy.uix.stacklayout import StackLayout
from kivy.uix.label import Label
from kivy.properties import ColorProperty
from kivy.uix.image import Image
import pkgutil
class TrackerLayout(BoxLayout):
pass
class CommanderSelect(BoxLayout):
pass
class CommanderButton(ToggleButton):
pass
class FactionBox(BoxLayout):
pass
class CommanderGroup(BoxLayout):
pass
class ItemTracker(BoxLayout):
pass
class ItemLabel(Label):
pass
class WargrooveManager(GameManager):
logging_pairs = [
("Client", "Archipelago"),
("WG", "WG Console"),
]
base_title = "Archipelago Wargroove Client"
ctx: WargrooveContext
unit_tracker: ItemTracker
trigger_tracker: BoxLayout
boost_tracker: BoxLayout
commander_buttons: Dict[int, List[CommanderButton]]
tracker_items = {
"Swordsman": ItemData(None, "Unit", False),
"Dog": ItemData(None, "Unit", False),
**item_table
}
def build(self):
container = super().build()
panel = TabbedPanelItem(text="Wargroove")
panel.content = self.build_tracker()
self.tabs.add_widget(panel)
return container
def build_tracker(self) -> TrackerLayout:
try:
tracker = TrackerLayout(orientation="horizontal")
commander_select = CommanderSelect(orientation="vertical")
self.commander_buttons = {}
for faction, commanders in faction_table.items():
faction_box = FactionBox(size_hint=(None, None), width=100 * len(commanders), height=70)
commander_group = CommanderGroup()
commander_buttons = []
for commander in commanders:
commander_button = CommanderButton(text=commander.name, group="commanders")
if faction == "Starter":
commander_button.disabled = False
commander_button.bind(on_press=lambda instance: self.ctx.set_commander(instance.text))
commander_buttons.append(commander_button)
commander_group.add_widget(commander_button)
self.commander_buttons[faction] = commander_buttons
faction_box.add_widget(Label(text=faction, size_hint_x=None, pos_hint={'left': 1}, size_hint_y=None, height=10))
faction_box.add_widget(commander_group)
commander_select.add_widget(faction_box)
item_tracker = ItemTracker(padding=[0,20])
self.unit_tracker = BoxLayout(orientation="vertical")
other_tracker = BoxLayout(orientation="vertical")
self.trigger_tracker = BoxLayout(orientation="vertical")
self.boost_tracker = BoxLayout(orientation="vertical")
other_tracker.add_widget(self.trigger_tracker)
other_tracker.add_widget(self.boost_tracker)
item_tracker.add_widget(self.unit_tracker)
item_tracker.add_widget(other_tracker)
tracker.add_widget(commander_select)
tracker.add_widget(item_tracker)
self.update_tracker()
return tracker
except Exception as e:
print(e)
def update_tracker(self):
received_ids = [item.item for item in self.ctx.items_received]
for faction, item_id in self.ctx.faction_item_ids.items():
for commander_button in self.commander_buttons[faction]:
commander_button.disabled = not (faction == "Starter" or item_id in received_ids)
self.unit_tracker.clear_widgets()
self.trigger_tracker.clear_widgets()
for name, item in self.tracker_items.items():
if item.type in ("Unit", "Trigger"):
status_color = (1, 1, 1, 1) if item.code is None or item.code in received_ids else (0.6, 0.2, 0.2, 1)
label = ItemLabel(text=name, color=status_color)
if item.type == "Unit":
self.unit_tracker.add_widget(label)
else:
self.trigger_tracker.add_widget(label)
self.boost_tracker.clear_widgets()
extra_income = received_ids.count(52023) * self.ctx.income_boost_multiplier
extra_defense = received_ids.count(52024) * self.ctx.commander_defense_boost_multiplier
income_boost = ItemLabel(text="Extra Income: " + str(extra_income))
defense_boost = ItemLabel(text="Comm Defense: " + str(100 + extra_defense))
self.boost_tracker.add_widget(income_boost)
self.boost_tracker.add_widget(defense_boost)
self.ui = WargrooveManager(self)
data = pkgutil.get_data(WargrooveWorld.__module__, "Wargroove.kv").decode()
Builder.load_string(data)
self.ui_task = asyncio.create_task(self.ui.async_run(), name="UI")
def update_commander_data(self):
if self.can_choose_commander:
faction_items = 0
faction_item_names = [faction + ' Commanders' for faction in faction_table.keys()]
for network_item in self.items_received:
if self.item_names[network_item.item] in faction_item_names:
faction_items += 1
starting_groove = (faction_items - 1) * self.starting_groove_multiplier
# Must be an integer larger than 0
starting_groove = int(max(starting_groove, 0))
data = {
"commander": self.current_commander.internal_name,
"starting_groove": starting_groove
}
else:
data = {
"commander": "seed",
"starting_groove": 0
}
filename = 'commander.json'
with open(os.path.join(self.game_communication_path, filename), 'w') as f:
json.dump(data, f)
if self.ui:
self.ui.update_tracker()
def set_commander(self, commander_name: str) -> bool:
"""Sets the current commander to the given one, if possible"""
if not self.can_choose_commander:
wg_logger.error("Cannot set commanders in this game mode.")
return
match_name = commander_name.lower()
for commander, unlocked in self.get_commanders():
if commander.name.lower() == match_name or commander.alt_name and commander.alt_name.lower() == match_name:
if unlocked:
self.current_commander = commander
self.syncing = True
wg_logger.info(f"Commander set to {commander.name}.")
self.update_commander_data()
return True
else:
wg_logger.error(f"Commander {commander.name} has not been unlocked.")
return False
else:
wg_logger.error(f"{commander_name} is not a recognized Wargroove commander.")
def get_commanders(self) -> List[Tuple[CommanderData, bool]]:
"""Gets a list of commanders with their unlocked status"""
commanders = []
received_ids = [item.item for item in self.items_received]
for faction in faction_table.keys():
unlocked = faction == 'Starter' or self.faction_item_ids[faction] in received_ids
commanders += [(commander, unlocked) for commander in faction_table[faction]]
return commanders
async def game_watcher(ctx: WargrooveContext):
from worlds.wargroove.Locations import location_table
while not ctx.exit_event.is_set():
if ctx.syncing == True:
sync_msg = [{'cmd': 'Sync'}]
if ctx.locations_checked:
sync_msg.append({"cmd": "LocationChecks", "locations": list(ctx.locations_checked)})
await ctx.send_msgs(sync_msg)
ctx.syncing = False
sending = []
victory = False
for root, dirs, files in os.walk(ctx.game_communication_path):
for file in files:
if file.find("send") > -1:
st = file.split("send", -1)[1]
sending = sending+[(int(st))]
if file.find("victory") > -1:
victory = True
ctx.locations_checked = sending
message = [{"cmd": 'LocationChecks', "locations": sending}]
await ctx.send_msgs(message)
if not ctx.finished_game and victory:
await ctx.send_msgs([{"cmd": "StatusUpdate", "status": ClientStatus.CLIENT_GOAL}])
ctx.finished_game = True
await asyncio.sleep(0.1)
def print_error_and_close(msg):
logger.error("Error: " + msg)
Utils.messagebox("Error", msg, error=True)
sys.exit(1)
if __name__ == '__main__':
async def main(args):
ctx = WargrooveContext(args.connect, args.password)
ctx.server_task = asyncio.create_task(server_loop(ctx), name="server loop")
if gui_enabled:
ctx.run_gui()
ctx.run_cli()
progression_watcher = asyncio.create_task(
game_watcher(ctx), name="WargrooveProgressionWatcher")
await ctx.exit_event.wait()
ctx.server_address = None
await progression_watcher
await ctx.shutdown()
import colorama
parser = get_base_parser(description="Wargroove Client, for text interfacing.")
args, rest = parser.parse_known_args()
colorama.init()
asyncio.run(main(args))
colorama.deinit()

View File

@@ -5,6 +5,7 @@ from typing import Counter, Optional, Dict, Any, Tuple
from uuid import UUID
from flask import render_template
from jinja2 import pass_context, runtime
from werkzeug.exceptions import abort
from MultiServer import Context, get_saving_second
@@ -83,9 +84,6 @@ def get_alttp_id(item_name):
return Items.item_table[item_name][2]
app.jinja_env.filters["location_name"] = lambda location: lookup_any_location_id_to_name.get(location, location)
app.jinja_env.filters['item_name'] = lambda id: lookup_any_item_id_to_name.get(id, id)
links = {"Bow": "Progressive Bow",
"Silver Arrows": "Progressive Bow",
"Silver Bow": "Progressive Bow",
@@ -237,6 +235,22 @@ def render_timedelta(delta: datetime.timedelta):
return f"{hours}:{minutes}"
@pass_context
def get_location_name(context: runtime.Context, loc: int) -> str:
context_locations = context.get("custom_locations", {})
return collections.ChainMap(lookup_any_location_id_to_name, context_locations).get(loc, loc)
@pass_context
def get_item_name(context: runtime.Context, item: int) -> str:
context_items = context.get("custom_items", {})
return collections.ChainMap(lookup_any_item_id_to_name, context_items).get(item, item)
app.jinja_env.filters["location_name"] = get_location_name
app.jinja_env.filters["item_name"] = get_item_name
_multidata_cache = {}
@@ -258,10 +272,23 @@ def get_static_room_data(room: Room):
# in > 100 players this can take a bit of time and is the main reason for the cache
locations: Dict[int, Dict[int, Tuple[int, int, int]]] = multidata['locations']
names: Dict[int, Dict[int, str]] = multidata["names"]
games = {}
groups = {}
custom_locations = {}
custom_items = {}
if "slot_info" in multidata:
games = {slot: slot_info.game for slot, slot_info in multidata["slot_info"].items()}
groups = {slot: slot_info.group_members for slot, slot_info in multidata["slot_info"].items()
if slot_info.type == SlotType.group}
for game in games.values():
if game in multidata["datapackage"]:
custom_locations.update(
{id: name for name, id in multidata["datapackage"][game]["location_name_to_id"].items()})
custom_items.update(
{id: name for name, id in multidata["datapackage"][game]["item_name_to_id"].items()})
elif "games" in multidata:
games = multidata["games"]
seed_checks_in_area = checks_in_area.copy()
use_door_tracker = False
@@ -282,7 +309,8 @@ def get_static_room_data(room: Room):
if playernumber not in groups}
saving_second = get_saving_second(multidata["seed_name"])
result = locations, names, use_door_tracker, player_checks_in_area, player_location_to_area, \
multidata["precollected_items"], multidata["games"], multidata["slot_data"], groups, saving_second
multidata["precollected_items"], games, multidata["slot_data"], groups, saving_second, \
custom_locations, custom_items
_multidata_cache[room.seed.id] = result
return result
@@ -309,7 +337,8 @@ def _get_player_tracker(tracker: UUID, tracked_team: int, tracked_player: int, w
# Collect seed information and pare it down to a single player
locations, names, use_door_tracker, seed_checks_in_area, player_location_to_area, \
precollected_items, games, slot_data, groups, saving_second = get_static_room_data(room)
precollected_items, games, slot_data, groups, saving_second, custom_locations, custom_items = \
get_static_room_data(room)
player_name = names[tracked_team][tracked_player - 1]
location_to_area = player_location_to_area[tracked_player]
inventory = collections.Counter()
@@ -351,7 +380,7 @@ def _get_player_tracker(tracker: UUID, tracked_team: int, tracked_player: int, w
seed_checks_in_area, checks_done, slot_data[tracked_player], saving_second)
else:
tracker = __renderGenericTracker(multisave, room, locations, inventory, tracked_team, tracked_player, player_name,
seed_checks_in_area, checks_done, saving_second)
seed_checks_in_area, checks_done, saving_second, custom_locations, custom_items)
return (saving_second - datetime.datetime.now().second) % 60 or 60, tracker
@@ -1194,7 +1223,7 @@ def __renderSC2WoLTracker(multisave: Dict[str, Any], room: Room, locations: Dict
def __renderGenericTracker(multisave: Dict[str, Any], room: Room, locations: Dict[int, Dict[int, Tuple[int, int, int]]],
inventory: Counter, team: int, player: int, playerName: str,
seed_checks_in_area: Dict[int, Dict[str, int]], checks_done: Dict[str, int],
saving_second: int) -> str:
saving_second: int, custom_locations: Dict[int, str], custom_items: Dict[int, str]) -> str:
checked_locations = multisave.get("location_checks", {}).get((team, player), set())
player_received_items = {}
@@ -1212,8 +1241,8 @@ def __renderGenericTracker(multisave: Dict[str, Any], room: Room, locations: Dic
player=player, team=team, room=room, player_name=playerName,
checked_locations=checked_locations,
not_checked_locations=set(locations[player]) - checked_locations,
received_items=player_received_items,
saving_second=saving_second)
received_items=player_received_items, saving_second=saving_second,
custom_items=custom_items, custom_locations=custom_locations)
@app.route('/tracker/<suuid:tracker>')
@@ -1223,7 +1252,8 @@ def getTracker(tracker: UUID):
if not room:
abort(404)
locations, names, use_door_tracker, seed_checks_in_area, player_location_to_area, \
precollected_items, games, slot_data, groups, saving_second = get_static_room_data(room)
precollected_items, games, slot_data, groups, saving_second, custom_locations, custom_items = \
get_static_room_data(room)
inventory = {teamnumber: {playernumber: collections.Counter() for playernumber in range(1, len(team) + 1) if playernumber not in groups}
for teamnumber, team in enumerate(names)}

View File

@@ -139,6 +139,12 @@ pokemon_rb_options:
# True for operating system default program
# Alternatively, a path to a program to open the .gb file with
rom_start: true
wargroove_options:
# Locate the Wargroove root directory on your system.
# This is used by the Wargroove client, so it knows where to send communication files to
root_directory: "C:/Program Files (x86)/Steam/steamapps/common/Wargroove"
zillion_options:
# File name of the Zillion US rom
rom_file: "Zillion (UE) [!].sms"

View File

@@ -45,6 +45,7 @@ apworlds: set = {
"Rogue Legacy",
"Donkey Kong Country 3",
"Super Mario World",
"Stardew Valley",
"Timespinner",
}

View File

@@ -1,6 +1,6 @@
import pathlib
import typing
import unittest
import pathlib
from argparse import Namespace
import Utils
@@ -112,6 +112,8 @@ class WorldTestBase(unittest.TestCase):
self.world_setup()
def world_setup(self, seed: typing.Optional[int] = None) -> None:
if type(self) is WorldTestBase:
return # setUp gets called for tests defined in the base class. We skip world_setup here.
if not hasattr(self, "game"):
raise NotImplementedError("didn't define game name")
self.multiworld = MultiWorld(1)
@@ -128,7 +130,9 @@ class WorldTestBase(unittest.TestCase):
for step in gen_steps:
call_all(self.multiworld, step)
# methods that can be called within tests
def collect_all_but(self, item_names: typing.Union[str, typing.Iterable[str]]) -> None:
"""Collects all pre-placed items and items in the multiworld itempool except those provided"""
if isinstance(item_names, str):
item_names = (item_names,)
for item in self.multiworld.get_items():
@@ -136,12 +140,14 @@ class WorldTestBase(unittest.TestCase):
self.multiworld.state.collect(item)
def get_item_by_name(self, item_name: str) -> Item:
"""Returns the first item found in placed items, or in the itempool with the matching name"""
for item in self.multiworld.get_items():
if item.name == item_name:
return item
raise ValueError("No such item")
def get_items_by_name(self, item_names: typing.Union[str, typing.Iterable[str]]) -> typing.List[Item]:
"""Returns actual items from the itempool that match the provided name(s)"""
if isinstance(item_names, str):
item_names = (item_names,)
return [item for item in self.multiworld.itempool if item.name in item_names]
@@ -153,12 +159,14 @@ class WorldTestBase(unittest.TestCase):
return items
def collect(self, items: typing.Union[Item, typing.Iterable[Item]]) -> None:
"""Collects the provided item(s) into state"""
if isinstance(items, Item):
items = (items,)
for item in items:
self.multiworld.state.collect(item)
def remove(self, items: typing.Union[Item, typing.Iterable[Item]]) -> None:
"""Removes the provided item(s) from state"""
if isinstance(items, Item):
items = (items,)
for item in items:
@@ -167,17 +175,22 @@ class WorldTestBase(unittest.TestCase):
self.multiworld.state.remove(item)
def can_reach_location(self, location: str) -> bool:
"""Determines if the current state can reach the provide location name"""
return self.multiworld.state.can_reach(location, "Location", 1)
def can_reach_entrance(self, entrance: str) -> bool:
"""Determines if the current state can reach the provided entrance name"""
return self.multiworld.state.can_reach(entrance, "Entrance", 1)
def count(self, item_name: str) -> int:
"""Returns the amount of an item currently in state"""
return self.multiworld.state.count(item_name, 1)
def assertAccessDependency(self,
locations: typing.List[str],
possible_items: typing.Iterable[typing.Iterable[str]]) -> None:
"""Asserts that the provided locations can't be reached without the listed items but can be reached with any
one of the provided combinations"""
all_items = [item_name for item_names in possible_items for item_name in item_names]
self.collect_all_but(all_items)
@@ -190,4 +203,36 @@ class WorldTestBase(unittest.TestCase):
self.remove(items)
def assertBeatable(self, beatable: bool):
"""Asserts that the game can be beaten with the current state"""
self.assertEqual(self.multiworld.can_beat_game(self.multiworld.state), beatable)
# following tests are automatically run
@property
def skip_default_tests(self) -> bool:
"""Not possible or identical to the base test that's always being run already"""
constructed = hasattr(self, "game") and hasattr(self, "multiworld")
return not constructed or (not self.options
and self.setUp is WorldTestBase.setUp
and self.world_setup is WorldTestBase.world_setup)
def testAllStateCanReachEverything(self):
"""Ensure all state can reach everything with the defined options"""
if self.skip_default_tests:
return
with self.subTest("Game", game=self.game):
excluded = self.multiworld.exclude_locations[1].value
state = self.multiworld.get_all_state(False)
for location in self.multiworld.get_locations():
if location.name not in excluded:
with self.subTest("Location should be reached", location=location):
self.assertTrue(location.can_reach(state), f"{location.name} unreachable")
def testEmptyStateCanReachSomething(self):
"""Ensure empty state can reach at least one location with the defined options"""
if self.skip_default_tests:
return
with self.subTest("Game", game=self.game):
state = CollectionState(self.multiworld)
locations = self.multiworld.get_reachable_locations(state, 1)
self.assertGreater(len(locations), 0,
"Need to be able to reach at least one location to get started.")

View File

@@ -8,7 +8,7 @@ class TestImplemented(unittest.TestCase):
def testCompletionCondition(self):
"""Ensure a completion condition is set that has requirements."""
for game_name, world_type in AutoWorldRegister.world_types.items():
if not world_type.hidden and game_name not in {"ArchipIDLE", "Sudoku"}:
if not world_type.hidden and game_name not in {"Sudoku"}:
with self.subTest(game_name):
multiworld = setup_solo_multiworld(world_type)
self.assertFalse(multiworld.completion_condition[1](multiworld.state))

View File

@@ -131,54 +131,69 @@ class World(metaclass=AutoWorldRegister):
"""A World object encompasses a game's Items, Locations, Rules and additional data or functionality required.
A Game should have its own subclass of World in which it defines the required data structures."""
option_definitions: ClassVar[Dict[str, AssembleOptions]] = {} # link your Options mapping
game: ClassVar[str] # name the game
topology_present: ClassVar[bool] = False # indicate if world type has any meaningful layout/pathing
option_definitions: ClassVar[Dict[str, AssembleOptions]] = {}
"""link your Options mapping"""
game: ClassVar[str]
"""name the game"""
topology_present: ClassVar[bool] = False
"""indicate if world type has any meaningful layout/pathing"""
# gets automatically populated with all item and item group names
all_item_and_group_names: ClassVar[FrozenSet[str]] = frozenset()
"""gets automatically populated with all item and item group names"""
# map names to their IDs
item_name_to_id: ClassVar[Dict[str, int]] = {}
"""map item names to their IDs"""
location_name_to_id: ClassVar[Dict[str, int]] = {}
"""map location names to their IDs"""
# maps item group names to sets of items. Example: "Weapons" -> {"Sword", "Bow"}
item_name_groups: ClassVar[Dict[str, Set[str]]] = {}
"""maps item group names to sets of items. Example: {"Weapons": {"Sword", "Bow"}}"""
# increment this every time something in your world's names/id mappings changes.
# While this is set to 0 in *any* AutoWorld, the entire DataPackage is considered in testing mode and will be
# retrieved by clients on every connection.
data_version: ClassVar[int] = 1
"""
increment this every time something in your world's names/id mappings changes.
While this is set to 0, this world's DataPackage is considered in testing mode and will be inserted to the multidata
and retrieved by clients on every connection.
"""
# override this if changes to a world break forward-compatibility of the client
# The base version of (0, 1, 6) is provided for backwards compatibility and does *not* need to be updated in the
# future. Protocol level compatibility check moved to MultiServer.min_client_version.
required_client_version: Tuple[int, int, int] = (0, 1, 6)
"""
override this if changes to a world break forward-compatibility of the client
The base version of (0, 1, 6) is provided for backwards compatibility and does *not* need to be updated in the
future. Protocol level compatibility check moved to MultiServer.min_client_version.
"""
# update this if the resulting multidata breaks forward-compatibility of the server
required_server_version: Tuple[int, int, int] = (0, 2, 4)
"""update this if the resulting multidata breaks forward-compatibility of the server"""
hint_blacklist: ClassVar[FrozenSet[str]] = frozenset() # any names that should not be hintable
hint_blacklist: ClassVar[FrozenSet[str]] = frozenset()
"""any names that should not be hintable"""
# Hide World Type from various views. Does not remove functionality.
hidden: ClassVar[bool] = False
"""Hide World Type from various views. Does not remove functionality."""
# see WebWorld for options
web: ClassVar[WebWorld] = WebWorld()
"""see WebWorld for options"""
# autoset on creation:
multiworld: "MultiWorld"
"""autoset on creation. The MultiWorld object for the currently generating multiworld."""
player: int
"""autoset on creation. The player number for this World"""
# automatically generated
item_id_to_name: ClassVar[Dict[int, str]]
"""automatically generated reverse lookup of item id to name"""
location_id_to_name: ClassVar[Dict[int, str]]
"""automatically generated reverse lookup of location id to name"""
item_names: ClassVar[Set[str]] # set of all potential item names
location_names: ClassVar[Set[str]] # set of all potential location names
item_names: ClassVar[Set[str]]
"""set of all potential item names"""
location_names: ClassVar[Set[str]]
"""set of all potential location names"""
zip_path: ClassVar[Optional[pathlib.Path]] = None # If loaded from a .apworld, this is the Path to it.
__file__: ClassVar[str] # path it was loaded from
zip_path: ClassVar[Optional[pathlib.Path]] = None
"""If loaded from a .apworld, this is the Path to it."""
__file__: ClassVar[str]
"""path it was loaded from"""
def __init__(self, multiworld: "MultiWorld", player: int):
self.multiworld = multiworld
@@ -196,18 +211,32 @@ class World(metaclass=AutoWorldRegister):
pass
def generate_early(self) -> None:
"""
Run before any general steps of the MultiWorld other than options. Useful for getting and adjusting option
results and determining layouts for entrance rando etc. start inventory gets pushed after this step.
"""
pass
def create_regions(self) -> None:
"""Method for creating and connecting regions for the World."""
pass
def create_items(self) -> None:
"""
Method for creating and submitting items to the itempool. Items and Regions should *not* be created and submitted
to the MultiWorld after this step. If items need to be placed during pre_fill use `get_prefill_items`.
"""
pass
def set_rules(self) -> None:
"""Method for setting the rules on the World's regions and locations."""
pass
def generate_basic(self) -> None:
"""
Useful for randomizing things that don't affect logic but are better to be determined before the output stage.
i.e. checking what the player has marked as priority or randomizing enemies
"""
pass
def pre_fill(self) -> None:

View File

@@ -3,6 +3,7 @@ import os
import random
import threading
import typing
from collections import OrderedDict
import Utils
from BaseClasses import Item, CollectionState, Tutorial, MultiWorld
@@ -19,8 +20,8 @@ from .Client import ALTTPSNIClient
from .Rom import LocalRom, patch_rom, patch_race_rom, check_enemizer, patch_enemizer, apply_rom_settings, \
get_hash_string, get_base_rom_path, LttPDeltaPatch
from .Rules import set_rules
from .Shops import create_shops, ShopSlotFill
from .SubClasses import ALttPItem
from .Shops import create_shops, ShopSlotFill, ShopType, price_rate_display, price_type_display_name
from .SubClasses import ALttPItem, LTTPRegionType
from worlds.AutoWorld import World, WebWorld, LogicMixin
lttp_logger = logging.getLogger("A Link to the Past")
@@ -520,6 +521,126 @@ class ALTTPWorld(World):
else:
logging.warning(f"Could not trash fill Ganon's Tower for player {player}.")
def write_spoiler_header(self, spoiler_handle: typing.TextIO) -> None:
def bool_to_text(variable: typing.Union[bool, str]) -> str:
if type(variable) == str:
return variable
return "Yes" if variable else "No"
spoiler_handle.write('Logic: %s\n' % self.multiworld.logic[self.player])
spoiler_handle.write('Dark Room Logic: %s\n' % self.multiworld.dark_room_logic[self.player])
spoiler_handle.write('Mode: %s\n' % self.multiworld.mode[self.player])
spoiler_handle.write('Goal: %s\n' % self.multiworld.goal[self.player])
if "triforce" in self.multiworld.goal[self.player]: # triforce hunt
spoiler_handle.write("Pieces available for Triforce: %s\n" %
self.multiworld.triforce_pieces_available[self.player])
spoiler_handle.write("Pieces required for Triforce: %s\n" %
self.multiworld.triforce_pieces_required[self.player])
spoiler_handle.write('Difficulty: %s\n' % self.multiworld.difficulty[self.player])
spoiler_handle.write('Item Functionality: %s\n' % self.multiworld.item_functionality[self.player])
spoiler_handle.write('Entrance Shuffle: %s\n' % self.multiworld.shuffle[self.player])
if self.multiworld.shuffle[self.player] != "vanilla":
spoiler_handle.write('Entrance Shuffle Seed %s\n' % self.er_seed)
spoiler_handle.write('Shop inventory shuffle: %s\n' %
bool_to_text("i" in self.multiworld.shop_shuffle[self.player]))
spoiler_handle.write('Shop price shuffle: %s\n' %
bool_to_text("p" in self.multiworld.shop_shuffle[self.player]))
spoiler_handle.write('Shop upgrade shuffle: %s\n' %
bool_to_text("u" in self.multiworld.shop_shuffle[self.player]))
spoiler_handle.write('New Shop inventory: %s\n' %
bool_to_text("g" in self.multiworld.shop_shuffle[self.player] or
"f" in self.multiworld.shop_shuffle[self.player]))
spoiler_handle.write('Custom Potion Shop: %s\n' %
bool_to_text("w" in self.multiworld.shop_shuffle[self.player]))
spoiler_handle.write('Enemy health: %s\n' % self.multiworld.enemy_health[self.player])
spoiler_handle.write('Enemy damage: %s\n' % self.multiworld.enemy_damage[self.player])
spoiler_handle.write('Prize shuffle %s\n' % self.multiworld.shuffle_prizes[self.player])
def write_spoiler(self, spoiler_handle: typing.TextIO) -> None:
spoiler_handle.write("\n\nMedallions:\n")
spoiler_handle.write(f"\nMisery Mire ({self.multiworld.get_player_name(self.player)}):"
f" {self.multiworld.required_medallions[self.player][0]}")
spoiler_handle.write(
f"\nTurtle Rock ({self.multiworld.get_player_name(self.player)}):"
f" {self.multiworld.required_medallions[self.player][1]}")
if self.multiworld.boss_shuffle[self.player] != "none":
def create_boss_map() -> typing.Dict:
boss_map = {
"Eastern Palace": self.multiworld.get_dungeon("Eastern Palace", self.player).boss.name,
"Desert Palace": self.multiworld.get_dungeon("Desert Palace", self.player).boss.name,
"Tower Of Hera": self.multiworld.get_dungeon("Tower of Hera", self.player).boss.name,
"Hyrule Castle": "Agahnim",
"Palace Of Darkness": self.multiworld.get_dungeon("Palace of Darkness",
self.player).boss.name,
"Swamp Palace": self.multiworld.get_dungeon("Swamp Palace", self.player).boss.name,
"Skull Woods": self.multiworld.get_dungeon("Skull Woods", self.player).boss.name,
"Thieves Town": self.multiworld.get_dungeon("Thieves Town", self.player).boss.name,
"Ice Palace": self.multiworld.get_dungeon("Ice Palace", self.player).boss.name,
"Misery Mire": self.multiworld.get_dungeon("Misery Mire", self.player).boss.name,
"Turtle Rock": self.multiworld.get_dungeon("Turtle Rock", self.player).boss.name,
"Ganons Tower": "Agahnim 2",
"Ganon": "Ganon"
}
if self.multiworld.mode[self.player] != 'inverted':
boss_map.update({
"Ganons Tower Basement":
self.multiworld.get_dungeon("Ganons Tower", self.player).bosses["bottom"].name,
"Ganons Tower Middle": self.multiworld.get_dungeon("Ganons Tower", self.player).bosses[
"middle"].name,
"Ganons Tower Top": self.multiworld.get_dungeon("Ganons Tower", self.player).bosses[
"top"].name
})
else:
boss_map.update({
"Ganons Tower Basement": self.multiworld.get_dungeon("Inverted Ganons Tower", self.player).bosses["bottom"].name,
"Ganons Tower Middle": self.multiworld.get_dungeon("Inverted Ganons Tower", self.player).bosses["middle"].name,
"Ganons Tower Top": self.multiworld.get_dungeon("Inverted Ganons Tower", self.player).bosses["top"].name
})
return boss_map
bossmap = create_boss_map()
spoiler_handle.write(
f'\n\nBosses{(f" ({self.multiworld.get_player_name(self.player)})" if self.multiworld.players > 1 else "")}:\n')
spoiler_handle.write(' ' + '\n '.join([f'{x}: {y}' for x, y in bossmap.items()]))
def build_shop_info() -> typing.Dict:
shop = self.multiworld.shops[self.player]
if not shop.custom:
return None
shop_data = {
"location": str(shop.region),
"type": "Take Any" if shop.type == ShopType.TakeAny else "Shop"
}
for index, item in enumerate(shop.inventory):
if item is None:
continue
price = item["price"] // price_rate_display.get(item["price_type"], 1)
shop_data["item_{}".format(index)] = f"{item['item']} - {price} {price_type_display_name[item['price_type']]}"
if item["player"]:
shop_data["item_{}".format(index)] =\
shop_data["item_{}".format(index)].replace("", "(Player {}) — ".format(item["player"]))
if item["max"] == 0:
continue
shop_data["item_{}".format(index)] += " x {}".format(item["max"])
if item["replacement"] is None:
continue
shop_data["item_{}".format(index)] +=\
f", {item['replacement']} - {item['replacement_price']}" \
f" {price_type_display_name[item['replacement_price_type']]}"
return shop_data
shop_data = build_shop_info()
if shop_data is not None:
spoiler_handle.write('\n\nShops:\n\n')
spoiler_handle.write(''.join("{} [{}]\n {}".format(shop_data['location'], shop_data['type'], "\n ".join(
item for item in [shop_data.get('item_0', None), shop_data.get('item_1', None), shop_data.get('item_2', None)] if
item))))
def get_filler_item_name(self) -> str:
if self.multiworld.goal[self.player] == "icerodhunt":
item = "Nothing"

View File

@@ -31,3 +31,7 @@ def set_rules(world: MultiWorld, player: int):
world.get_location(f"IDLE for at least {int(i / 2)} minutes {30 if (i % 2) else 0} seconds", player),
lambda state: state._archipidle_location_is_accessible(player, 20)
)
world.completion_condition[player] =\
lambda state:\
state.can_reach(world.get_location("IDLE for at least 50 minutes 0 seconds", player), "Location", player)

135
worlds/blasphemous/Exits.py Normal file
View File

@@ -0,0 +1,135 @@
from typing import List, Dict
region_exit_table: Dict[str, List[str]] = {
"menu" : ["New Game"],
"albero" : ["To The Holy Line",
"To Desecrated Cistern",
"To Wasteland of the Buried Churches",
"To Dungeons"],
"attots" : ["To Mother of Mothers"],
"ar" : ["To Mother of Mothers",
"To Wall of the Holy Prohibitions",
"To Deambulatory of His Holiness"],
"bottc" : ["To Wasteland of the Buried Churches",
"To Ferrous Tree"],
"botss" : ["To The Holy Line",
"To Mountains of the Endless Dusk"],
"coolotcv" : ["To Graveyard of the Peaks",
"To Wall of the Holy Prohibitions"],
"dohh" : ["To Archcathedral Rooftops"],
"dc" : ["To Albero",
"To Mercy Dreams",
"To Mountains of the Endless Dusk",
"To Echoes of Salt",
"To Grievance Ascends"],
"eos" : ["To Jondo",
"To Mountains of the Endless Dusk",
"To Desecrated Cistern",
"To The Resting Place of the Sister",
"To Mourning and Havoc"],
"ft" : ["To Bridge of the Three Cavalries",
"To Hall of the Dawning",
"To Patio of the Silent Steps"],
"gotp" : ["To Where Olive Trees Wither",
"To Convent of Our Lady of the Charred Visage"],
"ga" : ["To Jondo",
"To Desecrated Cistern"],
"hotd" : ["To Ferrous Tree"],
"jondo" : ["To Mountains of the Endless Dusk",
"To Grievance Ascends"],
"kottw" : ["To Mother of Mothers"],
"lotnw" : ["To Mother of Mothers",
"To The Sleeping Canvases"],
"md" : ["To Wasteland of the Buried Churches",
"To Desecrated Cistern",
"To The Sleeping Canvases"],
"mom" : ["To Patio of the Silent Steps",
"To Archcathedral Rooftops",
"To Knot of the Three Words",
"To Library of the Negated Words",
"To All the Tears of the Sea"],
"moted" : ["To Brotherhood of the Silent Sorrow",
"To Jondo",
"To Desecrated Cistern"],
"mah" : ["To Echoes of Salt",
"To Mother of Mothers"],
"potss" : ["To Ferrous Tree",
"To Mother of Mothers",
"To Wall of the Holy Prohibitions"],
"petrous" : ["To The Holy Line"],
"thl" : ["To Brotherhood of the Silent Sorrow",
"To Petrous",
"To Albero"],
"trpots" : ["To Echoes of Salt"],
"tsc" : ["To Library of the Negated Words",
"To Mercy Dreams"],
"wothp" : ["To Archcathedral Rooftops",
"To Convent of Our Lady of the Charred Visage"],
"wotbc" : ["To Albero",
"To Where Olive Trees Wither",
"To Mercy Dreams"],
"wotw" : ["To Wasteland of the Buried Churches",
"To Graveyard of the Peaks"]
}
exit_lookup_table: Dict[str, str] = {
"New Game": "botss",
"To Albero": "albero",
"To All the Tears of the Sea": "attots",
"To Archcathedral Rooftops": "ar",
"To Bridge of the Three Cavalries": "bottc",
"To Brotherhood of the Silent Sorrow": "botss",
"To Convent of Our Lady of the Charred Visage": "coolotcv",
"To Deambulatory of His Holiness": "dohh",
"To Desecrated Cistern": "dc",
"To Echoes of Salt": "eos",
"To Ferrous Tree": "ft",
"To Graveyard of the Peaks": "gotp",
"To Grievance Ascends": "ga",
"To Hall of the Dawning": "hotd",
"To Jondo": "jondo",
"To Knot of the Three Words": "kottw",
"To Library of the Negated Words": "lotnw",
"To Mercy Dreams": "md",
"To Mother of Mothers": "mom",
"To Mountains of the Endless Dusk": "moted",
"To Mourning and Havoc": "mah",
"To Patio of the Silent Steps": "potss",
"To Petrous": "petrous",
"To The Holy Line": "thl",
"To The Resting Place of the Sister": "trpots",
"To The Sleeping Canvases": "tsc",
"To Wall of the Holy Prohibitions": "wothp",
"To Wasteland of the Buried Churches": "wotbc",
"To Where Olive Trees Wither": "wotw",
"To Dungeons": "dungeon"
}

754
worlds/blasphemous/Items.py Normal file
View File

@@ -0,0 +1,754 @@
from BaseClasses import ItemClassification
from typing import TypedDict, Dict, List, Set
class ItemDict(TypedDict):
name: str
count: int
classification: ItemClassification
base_id = 1909000
item_table: List[ItemDict] = [
# Rosary Beads
{'name': "Dove Skull",
'count': 1,
'classification': ItemClassification.useful},
{'name': "Ember of the Holy Cremation",
'count': 1,
'classification': ItemClassification.useful},
{'name': "Silver Grape",
'count': 1,
'classification': ItemClassification.useful},
{'name': "Uvula of Proclamation",
'count': 1,
'classification': ItemClassification.useful},
{'name': "Hollow Pearl",
'count': 1,
'classification': ItemClassification.useful},
{'name': "Knot of Hair",
'count': 1,
'classification': ItemClassification.useful},
{'name': "Painted Wood Bead",
'count': 1,
'classification': ItemClassification.useful},
{'name': "Piece of a Golden Mask",
'count': 1,
'classification': ItemClassification.useful},
{'name': "Moss Preserved in Glass",
'count': 1,
'classification': ItemClassification.useful},
{'name': "Frozen Olive",
'count': 1,
'classification': ItemClassification.useful},
{'name': "Quirce's Scorched Bead",
'count': 1,
'classification': ItemClassification.useful},
{'name': "Wicker Knot",
'count': 1,
'classification': ItemClassification.useful},
{'name': "Perpetva's Protection",
'count': 1,
'classification': ItemClassification.useful},
{'name': "Thorned Symbol",
'count': 1,
'classification': ItemClassification.useful},
{'name': "Piece of a Tombstone",
'count': 1,
'classification': ItemClassification.useful},
{'name': "Sphere of the Sacred Smoke",
'count': 1,
'classification': ItemClassification.useful},
{'name': "Bead of Red Wax",
'count': 3,
'classification': ItemClassification.progression},
{'name': "Little Toe made of Limestone",
'count': 1,
'classification': ItemClassification.progression},
{'name': "Big Toe made of Limestone",
'count': 1,
'classification': ItemClassification.progression},
{'name': "Fourth Toe made of Limestone",
'count': 1,
'classification': ItemClassification.progression},
{'name': "Bead of Blue Wax",
'count': 3,
'classification': ItemClassification.progression},
{'name': "Pelican Effigy",
'count': 1,
'classification': ItemClassification.useful},
{'name': "Drop of Coagulated Ink",
'count': 1,
'classification': ItemClassification.useful},
{'name': "Amber Eye",
'count': 1,
'classification': ItemClassification.useful},
{'name': "Muted Bell",
'count': 1,
'classification': ItemClassification.useful},
{'name': "Consecrated Amethyst",
'count': 1,
'classification': ItemClassification.useful},
{'name': "Embers of a Broken Star",
'count': 1,
'classification': ItemClassification.useful},
{'name': "Scaly Coin",
'count': 1,
'classification': ItemClassification.useful},
{'name': "Seashell of the Inverted Spiral",
'count': 1,
'classification': ItemClassification.useful},
{'name': "Calcified Eye of Erudition",
'count': 1,
'classification': ItemClassification.useful},
{'name': "Weight of True Guilt",
'count': 1,
'classification': ItemClassification.progression},
{'name': "Reliquary of the Fervent Heart",
'count': 1,
'classification': ItemClassification.useful},
{'name': "Reliquary of the Suffering Heart",
'count': 1,
'classification': ItemClassification.useful},
{'name': "Reliquary of the Sorrowful Heart",
'count': 1,
'classification': ItemClassification.useful},
{'name': "Token of Appreciation",
'count': 1,
'classification': ItemClassification.useful},
{'name': "Cloistered Ruby",
'count': 1,
'classification': ItemClassification.progression},
{'name': "Bead of Gold Thread",
'count': 1,
'classification': ItemClassification.useful},
{'name': "Cloistered Sapphire",
'count': 1,
'classification': ItemClassification.useful},
{'name': "Fire Enclosed in Enamel",
'count': 1,
'classification': ItemClassification.useful},
{'name': "Light of the Lady of the Lamp",
'count': 1,
'classification': ItemClassification.useful},
{'name': "Scale of Burnished Alabaster",
'count': 1,
'classification': ItemClassification.useful},
{'name': "The Young Mason's Wheel",
'count': 1,
'classification': ItemClassification.progression},
{'name': "Crown of Gnawed Iron",
'count': 1,
'classification': ItemClassification.useful},
{'name': "Crimson Heart of a Miura",
'count': 1,
'classification': ItemClassification.useful},
# Prayers
{'name': "Seguiriya to your Eyes like Stars",
'count': 1,
'classification': ItemClassification.progression},
{'name': "Debla of the Lights",
'count': 1,
'classification': ItemClassification.progression},
{'name': "Saeta Dolorosa",
'count': 1,
'classification': ItemClassification.progression},
{'name': "Campanillero to the Sons of the Aurora",
'count': 1,
'classification': ItemClassification.progression},
{'name': "Lorquiana",
'count': 1,
'classification': ItemClassification.progression},
{'name': "Zarabanda of the Safe Haven",
'count': 1,
'classification': ItemClassification.progression},
{'name': "Taranto to my Sister",
'count': 1,
'classification': ItemClassification.progression},
{'name': "Solea of Excommunication",
'count': 1,
'classification': ItemClassification.progression},
{'name': "Tiento to your Thorned Hairs",
'count': 1,
'classification': ItemClassification.progression},
{'name': "Cante Jondo of the Three Sisters",
'count': 1,
'classification': ItemClassification.progression},
{'name': "Verdiales of the Forsaken Hamlet",
'count': 1,
'classification': ItemClassification.progression},
{'name': "Romance to the Crimson Mist",
'count': 1,
'classification': ItemClassification.progression},
{'name': "Zambra to the Resplendent Crown",
'count': 1,
'classification': ItemClassification.progression},
{'name': "Aubade of the Nameless Guardian",
'count': 1,
'classification': ItemClassification.progression},
{'name': "Cantina of the Blue Rose",
'count': 1,
'classification': ItemClassification.progression},
{'name': "Mirabras of the Return to Port",
'count': 1,
'classification': ItemClassification.progression},
{'name': "Tirana of the Celestial Bastion",
'count': 1,
'classification': ItemClassification.progression},
# Relics
{'name': "Blood Perpetuated in Sand",
'count': 1,
'classification': ItemClassification.progression},
{'name': "Incorrupt Hand of the Fraternal Master",
'count': 1,
'classification': ItemClassification.useful},
{'name': "Nail Uprooted from Dirt",
'count': 1,
'classification': ItemClassification.progression},
{'name': "Shroud of Dreamt Sins",
'count': 1,
'classification': ItemClassification.progression},
{'name': "Linen of Golden Thread",
'count': 1,
'classification': ItemClassification.progression},
{'name': "Silvered Lung of Dolphos",
'count': 1,
'classification': ItemClassification.progression},
{'name': "Three Gnarled Tongues",
'count': 1,
'classification': ItemClassification.progression},
# Mea Culpa Hearts
{'name': "Smoking Heart of Incense",
'count': 1,
'classification': ItemClassification.useful},
{'name': "Heart of the Virtuous Pain",
'count': 1,
'classification': ItemClassification.useful},
{'name': "Heart of Saltpeter Blood",
'count': 1,
'classification': ItemClassification.useful},
{'name': "Heart of Oils",
'count': 1,
'classification': ItemClassification.useful},
{'name': "Heart of Cerulean Incense",
'count': 1,
'classification': ItemClassification.useful},
{'name': "Heart of the Holy Purge",
'count': 1,
'classification': ItemClassification.useful},
{'name': "Molten Heart of Boiling Blood",
'count': 1,
'classification': ItemClassification.useful},
{'name': "Heart of the Single Tone",
'count': 1,
'classification': ItemClassification.useful},
{'name': "Heart of the Unnamed Minstrel",
'count': 1,
'classification': ItemClassification.useful},
{'name': "Brilliant Heart of Dawn",
'count': 1,
'classification': ItemClassification.progression},
{'name': "Apodictic Heart of Mea Culpa",
'count': 1,
'classification': ItemClassification.progression},
# Quest Items
{'name': "Cord of the True Burying",
'count': 1,
'classification': ItemClassification.progression},
{'name': "Mark of the First Refuge",
'count': 1,
'classification': ItemClassification.progression},
{'name': "Mark of the Second Refuge",
'count': 1,
'classification': ItemClassification.progression},
{'name': "Mark of the Third Refuge",
'count': 1,
'classification': ItemClassification.progression},
{'name': "Tentudia's Carnal Remains",
'count': 1,
'classification': ItemClassification.progression},
{'name': "Remains of Tentudia's Hair",
'count': 1,
'classification': ItemClassification.progression},
{'name': "Tentudia's Skeletal Remains",
'count': 1,
'classification': ItemClassification.progression},
{'name': "Melted Golden Coins",
'count': 1,
'classification': ItemClassification.progression},
{'name': "Torn Bridal Ribbon",
'count': 1,
'classification': ItemClassification.progression},
{'name': "Black Grieving Veil",
'count': 1,
'classification': ItemClassification.progression},
{'name': "Egg of Deformity",
'count': 1,
'classification': ItemClassification.progression},
{'name': "Hatched Egg of Deformity",
'count': 1,
'classification': ItemClassification.progression},
{'name': "Bouquet of Rosemary",
'count': 1,
'classification': ItemClassification.progression},
{'name': "Incense Garlic",
'count': 1,
'classification': ItemClassification.progression},
{'name': "Thorn Upgrade",
'count': 8,
'classification': ItemClassification.progression},
{'name': "Olive Seeds",
'count': 1,
'classification': ItemClassification.progression},
{'name': "Holy Wound of Attrition",
'count': 1,
'classification': ItemClassification.progression},
{'name': "Holy Wound of Contrition",
'count': 1,
'classification': ItemClassification.progression},
{'name': "Holy Wound of Compunction",
'count': 1,
'classification': ItemClassification.progression},
{'name': "Empty Bile Vessel",
'count': 8,
'classification': ItemClassification.progression},
{'name': "Knot of Rosary Rope",
'count': 6,
'classification': ItemClassification.progression},
{'name': "Golden Thimble Filled with Burning Oil",
'count': 1,
'classification': ItemClassification.progression},
{'name': "Key to the Chamber of the Eldest Brother",
'count': 1,
'classification': ItemClassification.progression},
{'name': "Empty Golden Thimble",
'count': 1,
'classification': ItemClassification.progression},
{'name': "Deformed Mask of Orestes",
'count': 1,
'classification': ItemClassification.progression},
{'name': "Mirrored Mask of Dolphos",
'count': 1,
'classification': ItemClassification.progression},
{'name': "Embossed Mask of Crescente",
'count': 1,
'classification': ItemClassification.progression},
{'name': "Dried Clove",
'count': 1,
'classification': ItemClassification.progression},
{'name': "Sooty Garlic",
'count': 1,
'classification': ItemClassification.progression},
{'name': "Bouquet of Thyme",
'count': 1,
'classification': ItemClassification.progression},
{'name': "Linen Cloth",
'count': 1,
'classification': ItemClassification.progression},
{'name': "Severed Hand",
'count': 1,
'classification': ItemClassification.progression},
{'name': "Dried Flowers bathed in Tears",
'count': 1,
'classification': ItemClassification.progression},
{'name': "Key of the Secular",
'count': 1,
'classification': ItemClassification.progression},
{'name': "Key of the Scribe",
'count': 1,
'classification': ItemClassification.progression},
{'name': "Key of the Inquisitor",
'count': 1,
'classification': ItemClassification.progression},
{'name': "Key of the High Peaks",
'count': 1,
'classification': ItemClassification.progression},
{'name': "Chalice of Inverted Verses",
'count': 1,
'classification': ItemClassification.progression},
{'name': "Quicksilver",
'count': 5,
'classification': ItemClassification.useful},
{'name': "Petrified Bell",
'count': 1,
'classification': ItemClassification.progression},
{'name': "Verses Spun from Gold",
'count': 4,
'classification': ItemClassification.progression},
{'name': "Severed Right Eye of the Traitor",
'count': 1,
'classification': ItemClassification.progression},
{'name': "Broken Left Eye of the Traitor",
'count': 1,
'classification': ItemClassification.progression},
{'name': "Incomplete Scapular",
'count': 1,
'classification': ItemClassification.progression},
{'name': "Key Grown from Twisted Wood",
'count': 1,
'classification': ItemClassification.progression},
{'name': "Holy Wound of Abnegation",
'count': 1,
'classification': ItemClassification.progression},
# Skills
{'name': "Combo Skill",
'count': 3,
'classification': ItemClassification.useful},
{'name': "Charged Skill",
'count': 3,
'classification': ItemClassification.progression},
{'name': "Ranged Skill",
'count': 3,
'classification': ItemClassification.progression},
{'name': "Dive Skill",
'count': 3,
'classification': ItemClassification.progression},
{'name': "Lunge Skill",
'count': 3,
'classification': ItemClassification.useful},
# Other
{'name': "Parietal bone of Lasser, the Inquisitor",
'count': 1,
'classification': ItemClassification.progression},
{'name': "Jaw of Ashgan, the Inquisitor",
'count': 1,
'classification': ItemClassification.progression},
{'name': "Cervical vertebra of Zicher, the Brewmaster",
'count': 1,
'classification': ItemClassification.progression},
{'name': "Clavicle of Dalhuisen, the Schoolchild",
'count': 1,
'classification': ItemClassification.progression},
{'name': "Sternum of Vitas, the Performer",
'count': 1,
'classification': ItemClassification.progression},
{'name': "Ribs of Sabnock, the Guardian",
'count': 1,
'classification': ItemClassification.progression},
{'name': "Vertebra of John, the Gambler",
'count': 1,
'classification': ItemClassification.progression},
{'name': "Scapula of Carlos, the Executioner",
'count': 1,
'classification': ItemClassification.progression},
{'name': "Humerus of McMittens, the Nurse",
'count': 1,
'classification': ItemClassification.progression},
{'name': "Ulna of Koke, the Troubadour",
'count': 1,
'classification': ItemClassification.progression},
{'name': "Radius of Helzer, the Poet",
'count': 1,
'classification': ItemClassification.progression},
{'name': "Frontal of Martinus, the Ropemaker",
'count': 1,
'classification': ItemClassification.progression},
{'name': "Metacarpus of Hodges, the Blacksmith",
'count': 1,
'classification': ItemClassification.progression},
{'name': "Phalanx of Arthur, the Sailor",
'count': 1,
'classification': ItemClassification.progression},
{'name': "Phalanx of Miriam, the Counsellor",
'count': 1,
'classification': ItemClassification.progression},
{'name': "Phalanx of Brannon, the Gravedigger",
'count': 1,
'classification': ItemClassification.progression},
{'name': "Coxal of June, the Prostitute",
'count': 1,
'classification': ItemClassification.progression},
{'name': "Sacrum of the Dark Warlock",
'count': 1,
'classification': ItemClassification.progression},
{'name': "Coccyx of Daniel, the Possessed",
'count': 1,
'classification': ItemClassification.progression},
{'name': "Femur of Karpow, the Bounty Hunter",
'count': 1,
'classification': ItemClassification.progression},
{'name': "Kneecap of Sebastien, the Puppeteer",
'count': 1,
'classification': ItemClassification.progression},
{'name': "Tibia of Alsahli, the Mystic",
'count': 1,
'classification': ItemClassification.progression},
{'name': "Fibula of Rysp, the Ranger",
'count': 1,
'classification': ItemClassification.progression},
{'name': "Temporal of Joel, the Thief",
'count': 1,
'classification': ItemClassification.progression},
{'name': "Metatarsus of Rikusyo, the Traveller",
'count': 1,
'classification': ItemClassification.progression},
{'name': "Phalanx of Zeth, the Prisoner",
'count': 1,
'classification': ItemClassification.progression},
{'name': "Phalanx of William, the Sceptic",
'count': 1,
'classification': ItemClassification.progression},
{'name': "Phalanx of Aralcarim, the Archivist",
'count': 1,
'classification': ItemClassification.progression},
{'name': "Occipital of Tequila, the Metalsmith",
'count': 1,
'classification': ItemClassification.progression},
{'name': "Maxilla of Tarradax, the Cleric",
'count': 1,
'classification': ItemClassification.progression},
{'name': "Nasal bone of Charles, the Artist",
'count': 1,
'classification': ItemClassification.progression},
{'name': "Hyoid bone of Senex, the Beggar",
'count': 1,
'classification': ItemClassification.progression},
{'name': "Vertebra of Lindquist, the Forger",
'count': 1,
'classification': ItemClassification.progression},
{'name': "Trapezium of Jeremiah, the Hangman",
'count': 1,
'classification': ItemClassification.progression},
{'name': "Trapezoid of Yeager, the Jeweller",
'count': 1,
'classification': ItemClassification.progression},
{'name': "Capitate of Barock, the Herald",
'count': 1,
'classification': ItemClassification.progression},
{'name': "Hamate of Vukelich, the Copyist",
'count': 1,
'classification': ItemClassification.progression},
{'name': "Pisiform of Hernandez, the Explorer",
'count': 1,
'classification': ItemClassification.progression},
{'name': "Triquetral of Luca, the Tailor",
'count': 1,
'classification': ItemClassification.progression},
{'name': "Lunate of Keiya, the Butcher",
'count': 1,
'classification': ItemClassification.progression},
{'name': "Scaphoid of Fierce, the Leper",
'count': 1,
'classification': ItemClassification.progression},
{'name': "Anklebone of Weston, the Pilgrim",
'count': 1,
'classification': ItemClassification.progression},
{'name': "Calcaneum of Persian, the Bandit",
'count': 1,
'classification': ItemClassification.progression},
{'name': "Navicular of Kahnnyhoo, the Murderer",
'count': 1,
'classification': ItemClassification.progression},
{'name': "Child of Moonlight",
'count': 38,
'classification': ItemClassification.progression},
{'name': "Life Upgrade",
'count': 6,
'classification': ItemClassification.progression},
{'name': "Fervour Upgrade",
'count': 6,
'classification': ItemClassification.progression},
{'name': "Mea Culpa Upgrade",
'count': 7,
'classification': ItemClassification.progression},
{'name': "Tears of Atonement (250)",
'count': 1,
'classification': ItemClassification.filler},
{'name': "Tears of Atonement (300)",
'count': 1,
'classification': ItemClassification.filler},
{'name': "Tears of Atonement (500)",
'count': 3,
'classification': ItemClassification.filler},
{'name': "Tears of Atonement (625)",
'count': 1,
'classification': ItemClassification.filler},
{'name': "Tears of Atonement (750)",
'count': 1,
'classification': ItemClassification.filler},
{'name': "Tears of Atonement (1000)",
'count': 4,
'classification': ItemClassification.filler},
{'name': "Tears of Atonement (1250)",
'count': 1,
'classification': ItemClassification.filler},
{'name': "Tears of Atonement (1500)",
'count': 1,
'classification': ItemClassification.filler},
{'name': "Tears of Atonement (1750)",
'count': 1,
'classification': ItemClassification.filler},
{'name': "Tears of Atonement (2000)",
'count': 2,
'classification': ItemClassification.filler},
{'name': "Tears of Atonement (2100)",
'count': 1,
'classification': ItemClassification.filler},
{'name': "Tears of Atonement (2500)",
'count': 1,
'classification': ItemClassification.filler},
{'name': "Tears of Atonement (2600)",
'count': 1,
'classification': ItemClassification.filler},
{'name': "Tears of Atonement (3000)",
'count': 2,
'classification': ItemClassification.filler},
{'name': "Tears of Atonement (4300)",
'count': 1,
'classification': ItemClassification.filler},
{'name': "Tears of Atonement (5000)",
'count': 4,
'classification': ItemClassification.filler},
{'name': "Tears of Atonement (5500)",
'count': 1,
'classification': ItemClassification.filler},
{'name': "Tears of Atonement (9000)",
'count': 1,
'classification': ItemClassification.filler},
{'name': "Tears of Atonement (10000)",
'count': 1,
'classification': ItemClassification.filler},
{'name': "Tears of Atonement (11250)",
'count': 1,
'classification': ItemClassification.filler},
{'name': "Tears of Atonement (18000)",
'count': 5,
'classification': ItemClassification.filler},
{'name': "Tears of Atonement (30000)",
'count': 1,
'classification': ItemClassification.filler}
]
group_table: Dict[str, Set[str]] = {
"wounds" : ["Holy Wound of Attrition",
"Holy Wound of Contrition",
"Holy Wound of Compunction"],
"masks" : ["Deformed Mask of Orestes",
"Mirrored Mask of Dolphos",
"Embossed Mask of Crescente"],
"tirso" : ["Bouquet of Rosemary",
"Incense Garlic",
"Olive Seeds",
"Dried Clove",
"Sooty Garlic",
"Bouquet of Thyme"],
"tentudia": ["Tentudia's Carnal Remains",
"Remains of Tentudia's Hair",
"Tentudia's Skeletal Remains"],
"egg" : ["Melted Golden Coins",
"Torn Bridal Ribbon",
"Black Grieving Veil"],
"bones" : ["Parietal bone of Lasser, the Inquisitor",
"Jaw of Ashgan, the Inquisitor",
"Cervical vertebra of Zicher, the Brewmaster",
"Clavicle of Dalhuisen, the Schoolchild",
"Sternum of Vitas, the Performer",
"Ribs of Sabnock, the Guardian",
"Vertebra of John, the Gambler",
"Scapula of Carlos, the Executioner",
"Humerus of McMittens, the Nurse",
"Ulna of Koke, the Troubadour",
"Radius of Helzer, the Poet",
"Frontal of Martinus, the Ropemaker",
"Metacarpus of Hodges, the Blacksmith",
"Phalanx of Arthur, the Sailor",
"Phalanx of Miriam, the Counsellor",
"Phalanx of Brannon, the Gravedigger",
"Coxal of June, the Prostitute",
"Sacrum of the Dark Warlock",
"Coccyx of Daniel, the Possessed",
"Femur of Karpow, the Bounty Hunter",
"Kneecap of Sebastien, the Puppeteer",
"Tibia of Alsahli, the Mystic",
"Fibula of Rysp, the Ranger",
"Temporal of Joel, the Thief",
"Metatarsus of Rikusyo, the Traveller",
"Phalanx of Zeth, the Prisoner",
"Phalanx of William, the Sceptic",
"Phalanx of Aralcarim, the Archivist",
"Occipital of Tequila, the Metalsmith",
"Maxilla of Tarradax, the Cleric",
"Nasal bone of Charles, the Artist",
"Hyoid bone of Senex, the Beggar",
"Vertebra of Lindquist, the Forger",
"Trapezium of Jeremiah, the Hangman",
"Trapezoid of Yeager, the Jeweller",
"Capitate of Barock, the Herald",
"Hamate of Vukelich, the Copyist",
"Pisiform of Hernandez, the Explorer",
"Triquetral of Luca, the Tailor",
"Lunate of Keiya, the Butcher",
"Scaphoid of Fierce, the Leper",
"Anklebone of Weston, the Pilgrim",
"Calcaneum of Persian, the Bandit",
"Navicular of Kahnnyhoo, the Murderer"],
"power" : ["Life Upgrade",
"Fervour Upgrade",
"Empty Bile Vessel",
"Quicksilver"],
"prayer" : ["Seguiriya to your Eyes like Stars",
"Debla of the Lights",
"Saeta Dolorosa",
"Campanillero to the Sons of the Aurora",
"Lorquiana",
"Zarabanda of the Safe Haven",
"Taranto to my Sister",
"Solea of Excommunication",
"Tiento to your Thorned Hairs",
"Cante Jondo of the Three Sisters",
"Verdiales of the Forsaken Hamlet",
"Romance to the Crimson Mist",
"Zambra to the Resplendent Crown",
"Cantina of the Blue Rose",
"Mirabras of the Return to Port"]
}
tears_set: Set[str] = [
"Tears of Atonement (500)",
"Tears of Atonement (625)",
"Tears of Atonement (750)",
"Tears of Atonement (1000)",
"Tears of Atonement (1250)",
"Tears of Atonement (1500)",
"Tears of Atonement (1750)",
"Tears of Atonement (2000)",
"Tears of Atonement (2100)",
"Tears of Atonement (2500)",
"Tears of Atonement (2600)",
"Tears of Atonement (3000)",
"Tears of Atonement (4300)",
"Tears of Atonement (5000)",
"Tears of Atonement (5500)",
"Tears of Atonement (9000)",
"Tears of Atonement (10000)",
"Tears of Atonement (11250)",
"Tears of Atonement (18000)",
"Tears of Atonement (30000)"
]
reliquary_set: Set[str] = [
"Reliquary of the Fervent Heart",
"Reliquary of the Suffering Heart",
"Reliquary of the Sorrowful Heart"
]
skill_set: Set[str] = [
"Combo Skill",
"Charged Skill",
"Ranged Skill",
"Dive Skill",
"Lunge Skill"
]

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,257 @@
from Options import Choice, Toggle, DefaultOnToggle, DeathLink
class PrieDieuWarp(DefaultOnToggle):
"""Automatically unlocks the ability to warp between Prie Dieu shrines."""
display_name = "Unlock Fast Travel"
class SkipCutscenes(DefaultOnToggle):
"""Automatically skips most cutscenes."""
display_name = "Auto Skip Cutscenes"
class CorpseHints(DefaultOnToggle):
"""Changes the 34 corpses in game to give various hints about item locations."""
display_name = "Corpse Hints"
class Difficulty(Choice):
"""Adjusts the logic required to defeat bosses.
Impossible: Removes all logic requirements for bosses. Good luck."""
display_name = "Difficulty"
option_easy = 0
option_normal = 1
option_hard = 2
option_impossible = 3
default = 1
class Penitence(Toggle):
"""Allows one of the three Penitences to be chosen at the beginning of the game."""
display_name = "Penitence"
class ExpertLogic(Toggle):
"""Expands the logic used by the randomizer to allow for some difficult and/or lesser known tricks."""
display_name = "Expert Logic"
class Ending(Choice):
"""Choose which ending is required to complete the game."""
display_name = "Ending"
option_any_ending = 0
option_ending_b = 1
option_ending_c = 2
default = 0
class ThornShuffle(Choice):
"""Shuffles the Thorn given by Deogracias and all Thorn upgrades into the item pool."""
display_name = "Shuffle Thorn"
option_anywhere = 0
option_local_only = 1
option_vanilla = 2
default = 0
class ReliquaryShuffle(DefaultOnToggle):
"""Adds the True Torment exclusive Reliquary rosary beads into the item pool."""
display_name = "Shuffle Penitence Rewards"
class CherubShuffle(DefaultOnToggle):
"""Shuffles Children of Moonlight into the item pool."""
display_name = "Shuffle Children of Moonlight"
class LifeShuffle(DefaultOnToggle):
"""Shuffles life upgrades from the Lady of the Six Sorrows into the item pool."""
display_name = "Shuffle Life Upgrades"
class FervourShuffle(DefaultOnToggle):
"""Shuffles fervour upgrades from the Oil of the Pilgrims into the item pool."""
display_name = "Shuffle Fervour Upgrades"
class SwordShuffle(DefaultOnToggle):
"""Shuffles Mea Culpa upgrades from the Mea Culpa Altars into the item pool."""
display_name = "Shuffle Mea Culpa Upgrades"
class BlessingShuffle(DefaultOnToggle):
"""Shuffles blessings from the Lake of Silent Pilgrims into the item pool."""
display_name = "Shuffle Blessings"
class DungeonShuffle(DefaultOnToggle):
"""Shuffles rewards from completing Confessor Dungeons into the item pool."""
display_name = "Shuffle Dungeon Rewards"
class TirsoShuffle(DefaultOnToggle):
"""Shuffles rewards from delivering herbs to Tirso into the item pool."""
display_name = "Shuffle Tirso's Rewards"
class MiriamShuffle(DefaultOnToggle):
"""Shuffles the prayer given by Miriam into the item pool."""
display_name = "Shuffle Miriram's Reward"
class RedentoShuffle(DefaultOnToggle):
"""Shuffles rewards from assisting Redento into the item pool."""
display_name = "Shuffle Redento's Rewards"
class JocineroShuffle(DefaultOnToggle):
"""Shuffles rewards from rescuing 20 and 38 Children of Moonlight into the item pool."""
display_name = "Shuffle Jocinero's Rewards"
class AltasgraciasShuffle(DefaultOnToggle):
"""Shuffles the reward given by Altasgracias and the item left behind by them into the item pool."""
display_name = "Shuffle Altasgracias' Rewards"
class TentudiaShuffle(DefaultOnToggle):
"""Shuffles the rewards from delivering Tentudia's remains to Lvdovico into the item pool."""
display_name = "Shuffle Lvdovico's Rewards"
class GeminoShuffle(DefaultOnToggle):
"""Shuffles the rewards from Gemino's quest and the hidden tomb into the item pool."""
display_name = "Shuffle Gemino's Rewards"
class GuiltShuffle(DefaultOnToggle):
"""Shuffles the Weight of True Guilt into the item pool."""
display_name = "Shuffle Immaculate Bead"
class OssuaryShuffle(DefaultOnToggle):
"""Shuffles the rewards from delivering bones to the Ossuary into the item pool."""
display_name = "Shuffle Ossuary Rewards"
class BossShuffle(DefaultOnToggle):
"""Shuffles the Tears of Atonement from defeating bosses into the item pool."""
display_name = "Shuffle Boss Tears"
class WoundShuffle(DefaultOnToggle):
"""Shuffles the Holy Wounds required to pass the Bridge of the Three Cavalries into the item pool."""
display_name = "Shuffle Holy Wounds"
class MaskShuffle(DefaultOnToggle):
"""Shuffles the masks required to use the elevator in Archcathedral Rooftops into the item pool."""
display_name = "Shuffle Masks"
class EyeShuffle(DefaultOnToggle):
"""Shuffles the Eyes of the Traitor from defeating Isidora and Sierpes into the item pool."""
display_name = "Shuffle Traitor's Eyes"
class HerbShuffle(DefaultOnToggle):
"""Shuffles the herbs required for Tirso's quest into the item pool."""
display_name = "Shuffle Herbs"
class ChurchShuffle(DefaultOnToggle):
"""Shuffles the rewards from donating 5,000 and 50,000 Tears of Atonement to the Church in Albero into the item pool."""
display_name = "Shuffle Donation Rewards"
class ShopShuffle(DefaultOnToggle):
"""Shuffles the items sold in Candelaria's shops into the item pool."""
display_name = "Shuffle Shop Items"
class CandleShuffle(DefaultOnToggle):
"""Shuffles the Beads of Wax and their upgrades into the item pool."""
display_name = "Shuffle Candles"
class StartWheel(Toggle):
"""Changes the beginning gift to The Young Mason's Wheel."""
display_name = "Start with Wheel"
class SkillRando(Toggle):
"""Randomizes the abilities from the skill tree into the item pool."""
display_name = "Skill Randomizer"
class EnemyRando(Choice):
"""Randomizes the enemies that appear in each room.
Shuffled: Enemies will be shuffled amongst each other, but can only appear as many times as they do in a standard game.
Randomized: Every enemy is completely random, and can appear any number of times.
Some enemies will never be randomized."""
display_name = "Enemy Randomizer"
option_disabled = 0
option_shuffled = 1
option_randomized = 2
default = 0
class EnemyGroups(DefaultOnToggle):
"""Randomized enemies will chosen from sets of specific groups.
(Weak, normal, large, flying)
Has no effect if Enemy Randomizer is disabled."""
display_name = "Enemy Groups"
class EnemyScaling(DefaultOnToggle):
"""Randomized enemies will have their stats increased or decreased depending on the area they appear in.
Has no effect if Enemy Randomizer is disabled."""
display_name = "Enemy Scaling"
class BlasphemousDeathLink(DeathLink):
"""When you die, everyone dies. The reverse is also true.
Note that Guilt Fragments will not appear when killed by Death Link."""
blasphemous_options = {
"prie_dieu_warp": PrieDieuWarp,
"skip_cutscenes": SkipCutscenes,
"corpse_hints": CorpseHints,
"difficulty": Difficulty,
"penitence": Penitence,
"expert_logic": ExpertLogic,
"ending": Ending,
"thorn_shuffle" : ThornShuffle,
"reliquary_shuffle": ReliquaryShuffle,
"cherub_shuffle" : CherubShuffle,
"life_shuffle" : LifeShuffle,
"fervour_shuffle" : FervourShuffle,
"sword_shuffle" : SwordShuffle,
"blessing_shuffle" : BlessingShuffle,
"dungeon_shuffle" : DungeonShuffle,
"tirso_shuffle" : TirsoShuffle,
"miriam_shuffle" : MiriamShuffle,
"redento_shuffle" : RedentoShuffle,
"jocinero_shuffle" : JocineroShuffle,
"altasgracias_shuffle" : AltasgraciasShuffle,
"tentudia_shuffle" : TentudiaShuffle,
"gemino_shuffle" : GeminoShuffle,
"guilt_shuffle" : GuiltShuffle,
"ossuary_shuffle" : OssuaryShuffle,
"boss_shuffle" : BossShuffle,
"wound_shuffle" : WoundShuffle,
"mask_shuffle" : MaskShuffle,
"eye_shuffle": EyeShuffle,
"herb_shuffle" : HerbShuffle,
"church_shuffle" : ChurchShuffle,
"shop_shuffle" : ShopShuffle,
"candle_shuffle" : CandleShuffle,
"start_wheel": StartWheel,
"skill_randomizer": SkillRando,
"enemy_randomizer": EnemyRando,
"enemy_groups": EnemyGroups,
"enemy_scaling": EnemyScaling,
"death_link": BlasphemousDeathLink
}

1455
worlds/blasphemous/Rules.py Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,246 @@
from typing import Set, Dict
unrandomized_dict: Dict[str, str] = {
"CoOLotCV: Fountain of burning oil": "Golden Thimble Filled with Burning Oil",
"MotED: Egg hatching": "Hatched Egg of Deformity",
"BotSS: Crisanta's gift": "Holy Wound of Abnegation",
"DC: Chalice room": "Chalice of Inverted Verses"
}
cherub_set: Set[str] = [
"Albero: Child of Moonlight",
"AR: Upper west shaft Child of Moonlight",
"BotSS: Starting room Child of Moonlight",
"DC: Child of Moonlight, above water",
"DC: Upper east Child of Moonlight",
"DC: Child of Moonlight, miasma room",
"DC: Child of Moonlight, behind pillar",
"DC: Top of elevator Child of Moonlight",
"DC: Elevator shaft Child of Moonlight",
"GotP: Shop cave Child of Moonlight",
"GotP: Elevator shaft Child of Moonlight",
"GotP: West shaft Child of Moonlight",
"GotP: Center shaft Child of Moonlight",
"GA: Miasma room Child of Moonlight",
"GA: Blood bridge Child of Moonlight",
"GA: Lower east Child of Moonlight",
"Jondo: Upper east Child of Moonlight",
"Jondo: Spike tunnel Child of Moonlight",
"Jondo: Upper west Child of Moonlight",
"LotNW: Platform room Child of Moonlight",
"LotNW: Lowest west Child of Moonlight",
"LotNW: Elevator Child of Moonlight",
"MD: Second area Child of Moonlight",
"MD: Cave Child of Moonlight",
"MoM: Lower west Child of Moonlight",
"MoM: Upper center Child of Moonlight",
"MotED: Child of Moonlight, above chasm",
"PotSS: First area Child of Moonlight",
"PotSS: Third area Child of Moonlight",
"THL: Child of Moonlight",
"WotHP: Upper east room, top bronze cell",
"WotHP: Upper west room, top silver cell",
"WotHP: Lower east room, bottom silver cell",
"WotHP: Outside Child of Moonlight",
"WotBC: Outside Child of Moonlight",
"WotBC: Cliffside Child of Moonlight",
"WOTW: Underground Child of Moonlight",
"WOTW: Upper east Child of Moonlight",
]
life_set: Set[str] = [
"AR: Lady of the Six Sorrows",
"CoOLotCV: Lady of the Six Sorrows",
"DC: Lady of the Six Sorrows, from MD",
"DC: Lady of the Six Sorrows, elevator shaft",
"GotP: Lady of the Six Sorrows",
"LotNW: Lady of the Six Sorrows"
]
fervour_set: Set[str] = [
"DC: Oil of the Pilgrims",
"GotP: Oil of the Pilgrims",
"GA: Oil of the Pilgrims",
"LotNW: Oil of the Pilgrims",
"MoM: Oil of the Pilgrims",
"WotHP: Oil of the Pilgrims"
]
sword_set: Set[str] = [
"Albero: Mea Culpa altar",
"AR: Mea Culpa altar",
"BotSS: Mea Culpa altar",
"CoOLotCV: Mea Culpa altar",
"DC: Mea Culpa altar",
"LotNW: Mea Culpa altar",
"MoM: Mea Culpa altar"
]
blessing_dict: Dict[str, str] = {
"Albero: Bless Severed Hand": "Incorrupt Hand of the Fraternal Master",
"Albero: Bless Linen Cloth": "Shroud of Dreamt Sins",
"Albero: Bless Hatched Egg": "Three Gnarled Tongues"
}
dungeon_dict: Dict[str, str] = {
"Confessor Dungeon 1 extra": "Tears of Atonement (1000)",
"Confessor Dungeon 2 extra": "Heart of the Single Tone",
"Confessor Dungeon 3 extra": "Tears of Atonement (3000)",
"Confessor Dungeon 4 extra": "Embers of a Broken Star",
"Confessor Dungeon 5 extra": "Tears of Atonement (5000)",
"Confessor Dungeon 6 extra": "Scaly Coin",
"Confessor Dungeon 7 extra": "Seashell of the Inverted Spiral"
}
tirso_dict: Dict[str, str] = {
"Albero: Tirso's 1st reward": "Linen Cloth",
"Albero: Tirso's 2nd reward": "Tears of Atonement (500)",
"Albero: Tirso's 3rd reward": "Tears of Atonement (1000)",
"Albero: Tirso's 4th reward": "Tears of Atonement (2000)",
"Albero: Tirso's 5th reward": "Tears of Atonement (5000)",
"Albero: Tirso's 6th reward": "Tears of Atonement (10000)",
"Albero: Tirso's final reward": "Knot of Rosary Rope"
}
redento_dict: Dict[str, str] = {
"MoM: Redento's treasure": "Nail Uprooted from Dirt",
"MoM: Final meeting with Redento": "Knot of Rosary Rope",
"MotED: 1st meeting with Redento": "Fourth Toe made of Limestone",
"PotSS: 4th meeting with Redento": "Big Toe made of Limestone",
"WotBC: 3rd meeting with Redento": "Little Toe made of Limestone"
}
jocinero_dict: Dict[str, str] = {
"TSC: Jocinero's 1st reward": "Linen of Golden Thread",
"TSC: Jocinero's final reward": "Campanillero to the Sons of the Aurora"
}
altasgracias_dict: Dict[str, str] = {
"GA: Altasgracias' gift": "Egg of Deformity",
"GA: Empty giant egg": "Knot of Hair"
}
tentudia_dict: Dict[str, str] = {
"Albero: Lvdovico's 1st reward": "Tears of Atonement (500)",
"Albero: Lvdovico's 2nd reward": "Tears of Atonement (1000)",
"Albero: Lvdovico's 3rd reward": "Debla of the Lights"
}
gemino_dict: Dict[str, str] = {
"WOTW: Gift for the tomb": "Dried Flowers bathed in Tears",
"WOTW: Underground tomb": "Saeta Dolorosa",
"WOTW: Gemino's gift": "Empty Golden Thimble",
"WOTW: Gemino's reward": "Frozen Olive"
}
ossuary_dict: Dict[str, str] = {
"Ossuary: 1st reward": "Tears of Atonement (250)",
"Ossuary: 2nd reward": "Tears of Atonement (500)",
"Ossuary: 3rd reward": "Tears of Atonement (750)",
"Ossuary: 4th reward": "Tears of Atonement (1000)",
"Ossuary: 5th reward": "Tears of Atonement (1250)",
"Ossuary: 6th reward": "Tears of Atonement (1500)",
"Ossuary: 7th reward": "Tears of Atonement (1750)",
"Ossuary: 8th reward": "Tears of Atonement (2000)",
"Ossuary: 9th reward": "Tears of Atonement (2500)",
"Ossuary: 10th reward": "Tears of Atonement (3000)",
"Ossuary: 11th reward": "Tears of Atonement (5000)",
}
boss_dict: Dict[str, str] = {
"BotTC: Esdras, of the Anointed Legion": "Tears of Atonement (4300)",
"BotSS: Warden of the Silent Sorrow": "Tears of Atonement (300)",
"CoOLotCV: Our Lady of the Charred Visage": "Tears of Atonement (2600)",
"HotD: Laudes, the First of the Amanecidas": "Tears of Atonement (30000)",
"GotP: Amanecida of the Bejeweled Arrow": "Tears of Atonement (18000)",
"GA: Tres Angustias": "Tears of Atonement (2100)",
"MD: Ten Piedad": "Tears of Atonement (625)",
"MoM: Melquiades, The Exhumed Archbishop": "Tears of Atonement (5500)",
"MotED: Amanecida of the Golden Blades": "Tears of Atonement (18000)",
"MaH: Sierpes": "Tears of Atonement (5000)",
"PotSS: Amanecida of the Chiselled Steel": "Tears of Atonement (18000)",
"TSC: Exposito, Scion of Abjuration": "Tears of Atonement (9000)",
"WotHP: Quirce, Returned By The Flames": "Tears of Atonement (11250)",
"WotHP: Amanecida of the Molten Thorn": "Tears of Atonement (18000)"
}
wound_dict: Dict[str, str] = {
"CoOLotCV: Visage of Compunction": "Holy Wound of Compunction",
"GA: Visage of Contrition": "Holy Wound of Contrition",
"MD: Visage of Attrition": "Holy Wound of Attrition"
}
mask_dict: Dict[str, str] = {
"CoOLotCV: Mask room": "Mirrored Mask of Dolphos",
"LotNW: Mask room": "Embossed Mask of Crescente",
"MoM: Mask room": "Deformed Mask of Orestes"
}
eye_dict: Dict[str, str] = {
"Ossuary: Isidora, Voice of the Dead": "Severed Right Eye of the Traitor",
"MaH: Sierpes' eye": "Broken Left Eye of the Traitor"
}
herb_dict: Dict[str, str] = {
"Albero: Gate of Travel room": "Bouquet of Thyme",
"Jondo: Lower east bell trap": "Bouquet of Rosemary",
"MotED: Blood platform alcove": "Dried Clove",
"PotSS: Third area lower ledge": "Olive Seeds",
"TSC: Painting ladder ledge": "Sooty Garlic",
"WOTW: Entrance to tomb": "Incense Garlic"
}
church_dict: Dict[str, str] = {
"Albero: Donate 5000 Tears": "Token of Appreciation",
"Albero: Donate 50000 Tears": "Cloistered Ruby"
}
shop_dict: Dict[str, str] = {
"GotP: Shop item 1": "Torn Bridal Ribbon",
"GotP: Shop item 2": "Calcified Eye of Erudition",
"GotP: Shop item 3": "Ember of the Holy Cremation",
"MD: Shop item 1": "Key to the Chamber of the Eldest Brother",
"MD: Shop item 2": "Hollow Pearl",
"MD: Shop item 3": "Moss Preserved in Glass",
"TSC: Shop item 1": "Wicker Knot",
"TSC: Shop item 2": "Empty Bile Vessel",
"TSC: Shop item 3": "Key of the Inquisitor"
}
thorn_set: Set[str] = {
"THL: Deogracias' gift",
"Confessor Dungeon 1 main",
"Confessor Dungeon 2 main",
"Confessor Dungeon 3 main",
"Confessor Dungeon 4 main",
"Confessor Dungeon 5 main",
"Confessor Dungeon 6 main",
"Confessor Dungeon 7 main",
}
candle_dict: Dict[str, str] = {
"CoOLotCV: Red candle": "Bead of Red Wax",
"LotNW: Red candle": "Bead of Red Wax",
"MD: Red candle": "Bead of Red Wax",
"BotSS: Blue candle": "Bead of Blue Wax",
"CoOLotCV: Blue candle": "Bead of Blue Wax",
"MD: Blue candle": "Bead of Blue Wax"
}
skill_dict: Dict[str, str] = {
"Skill 1, Tier 1": "Combo Skill",
"Skill 1, Tier 2": "Combo Skill",
"Skill 1, Tier 3": "Combo Skill",
"Skill 2, Tier 1": "Charged Skill",
"Skill 2, Tier 2": "Charged Skill",
"Skill 2, Tier 3": "Charged Skill",
"Skill 3, Tier 1": "Ranged Skill",
"Skill 3, Tier 2": "Ranged Skill",
"Skill 3, Tier 3": "Ranged Skill",
"Skill 4, Tier 1": "Dive Skill",
"Skill 4, Tier 2": "Dive Skill",
"Skill 4, Tier 3": "Dive Skill",
"Skill 5, Tier 1": "Lunge Skill",
"Skill 5, Tier 2": "Lunge Skill",
"Skill 5, Tier 3": "Lunge Skill",
}

View File

@@ -0,0 +1,413 @@
from typing import Dict, Set, Any
from collections import Counter
from BaseClasses import Region, Entrance, Location, Item, Tutorial, ItemClassification
from ..AutoWorld import World, WebWorld
from .Items import base_id, item_table, group_table, tears_set, reliquary_set, skill_set
from .Locations import location_table, shop_set
from .Exits import region_exit_table, exit_lookup_table
from .Rules import rules
from worlds.generic.Rules import set_rule
from .Options import blasphemous_options
from . import Vanilla
class BlasphemousWeb(WebWorld):
theme = "stone"
tutorials = [Tutorial(
"Multiworld Setup Guide",
"A guide to setting up the Blasphemous randomizer connected to an Archipelago Multiworld",
"English",
"setup_en.md",
"setup/en",
["TRPG"]
)]
class BlasphemousWorld(World):
"""
Blasphemous is a challenging Metroidvania set in the cursed land of Cvstodia. Play as the Penitent One, trapped
in an endless cycle of death and rebirth, and free the world from it's terrible fate in your quest to break
your eternal damnation!
"""
game: str = "Blasphemous"
web = BlasphemousWeb()
data_version: 1
item_name_to_id = {item["name"]: (base_id + index) for index, item in enumerate(item_table)}
location_name_to_id = {loc["name"]: (base_id + index) for index, loc in enumerate(location_table)}
location_name_to_game_id = {loc["name"]: loc["game_id"] for loc in location_table}
item_name_groups = group_table
option_definitions = blasphemous_options
def set_rules(self):
rules(self)
def create_item(self, name: str) -> "BlasphemousItem":
item_id: int = self.item_name_to_id[name]
id = item_id - base_id
return BlasphemousItem(name, item_table[id]["classification"], item_id, player=self.player)
def create_event(self, event: str):
return BlasphemousItem(event, ItemClassification.progression_skip_balancing, None, self.player)
def get_filler_item_name(self) -> str:
return self.multiworld.random.choice(tears_set)
def generate_basic(self):
placed_items = []
placed_items.extend(Vanilla.unrandomized_dict.values())
if not self.multiworld.reliquary_shuffle[self.player]:
placed_items.extend(reliquary_set)
elif self.multiworld.reliquary_shuffle[self.player]:
placed_items.append("Tears of Atonement (250)")
placed_items.append("Tears of Atonement (300)")
placed_items.append("Tears of Atonement (500)")
if not self.multiworld.cherub_shuffle[self.player]:
for i in range(38):
placed_items.append("Child of Moonlight")
if not self.multiworld.life_shuffle[self.player]:
for i in range(6):
placed_items.append("Life Upgrade")
if not self.multiworld.fervour_shuffle[self.player]:
for i in range(6):
placed_items.append("Fervour Upgrade")
if not self.multiworld.sword_shuffle[self.player]:
for i in range(7):
placed_items.append("Mea Culpa Upgrade")
if not self.multiworld.blessing_shuffle[self.player]:
placed_items.extend(Vanilla.blessing_dict.values())
if not self.multiworld.dungeon_shuffle[self.player]:
placed_items.extend(Vanilla.dungeon_dict.values())
if not self.multiworld.tirso_shuffle[self.player]:
placed_items.extend(Vanilla.tirso_dict.values())
if not self.multiworld.miriam_shuffle[self.player]:
placed_items.append("Cantina of the Blue Rose")
if not self.multiworld.redento_shuffle[self.player]:
placed_items.extend(Vanilla.redento_dict.values())
if not self.multiworld.jocinero_shuffle[self.player]:
placed_items.extend(Vanilla.jocinero_dict.values())
if not self.multiworld.altasgracias_shuffle[self.player]:
placed_items.extend(Vanilla.altasgracias_dict.values())
if not self.multiworld.tentudia_shuffle[self.player]:
placed_items.extend(Vanilla.tentudia_dict.values())
if not self.multiworld.gemino_shuffle[self.player]:
placed_items.extend(Vanilla.gemino_dict.values())
if not self.multiworld.guilt_shuffle[self.player]:
placed_items.append("Weight of True Guilt")
if not self.multiworld.ossuary_shuffle[self.player]:
placed_items.extend(Vanilla.ossuary_dict.values())
if not self.multiworld.boss_shuffle[self.player]:
placed_items.extend(Vanilla.boss_dict.values())
if not self.multiworld.wound_shuffle[self.player]:
placed_items.extend(Vanilla.wound_dict.values())
if not self.multiworld.mask_shuffle[self.player]:
placed_items.extend(Vanilla.mask_dict.values())
if not self.multiworld.eye_shuffle[self.player]:
placed_items.extend(Vanilla.eye_dict.values())
if not self.multiworld.herb_shuffle[self.player]:
placed_items.extend(Vanilla.herb_dict.values())
if not self.multiworld.church_shuffle[self.player]:
placed_items.extend(Vanilla.church_dict.values())
if not self.multiworld.shop_shuffle[self.player]:
placed_items.extend(Vanilla.shop_dict.values())
if self.multiworld.thorn_shuffle[self.player] == 2:
for i in range(8):
placed_items.append("Thorn Upgrade")
if not self.multiworld.candle_shuffle[self.player]:
placed_items.extend(Vanilla.candle_dict.values())
if self.multiworld.start_wheel[self.player]:
placed_items.append("The Young Mason's Wheel")
if not self.multiworld.skill_randomizer[self.player]:
placed_items.extend(Vanilla.skill_dict.values())
counter = Counter(placed_items)
pool = []
for item in item_table:
count = item["count"] - counter[item["name"]]
if count <= 0:
continue
else:
for i in range(count):
pool.append(self.create_item(item["name"]))
self.multiworld.itempool += pool
def pre_fill(self):
self.place_items_from_dict(Vanilla.unrandomized_dict)
if not self.multiworld.cherub_shuffle[self.player]:
self.place_items_from_set(Vanilla.cherub_set, "Child of Moonlight")
if not self.multiworld.life_shuffle[self.player]:
self.place_items_from_set(Vanilla.life_set, "Life Upgrade")
if not self.multiworld.fervour_shuffle[self.player]:
self.place_items_from_set(Vanilla.fervour_set, "Fervour Upgrade")
if not self.multiworld.sword_shuffle[self.player]:
self.place_items_from_set(Vanilla.sword_set, "Mea Culpa Upgrade")
if not self.multiworld.blessing_shuffle[self.player]:
self.place_items_from_dict(Vanilla.blessing_dict)
if not self.multiworld.dungeon_shuffle[self.player]:
self.place_items_from_dict(Vanilla.dungeon_dict)
if not self.multiworld.tirso_shuffle[self.player]:
self.place_items_from_dict(Vanilla.tirso_dict)
if not self.multiworld.miriam_shuffle[self.player]:
self.multiworld.get_location("AtTotS: Miriam's gift", self.player)\
.place_locked_item(self.create_item("Cantina of the Blue Rose"))
if not self.multiworld.redento_shuffle[self.player]:
self.place_items_from_dict(Vanilla.redento_dict)
if not self.multiworld.jocinero_shuffle[self.player]:
self.place_items_from_dict(Vanilla.jocinero_dict)
if not self.multiworld.altasgracias_shuffle[self.player]:
self.place_items_from_dict(Vanilla.altasgracias_dict)
if not self.multiworld.tentudia_shuffle[self.player]:
self.place_items_from_dict(Vanilla.tentudia_dict)
if not self.multiworld.gemino_shuffle[self.player]:
self.place_items_from_dict(Vanilla.gemino_dict)
if not self.multiworld.guilt_shuffle[self.player]:
self.multiworld.get_location("GotP: Confessor Dungeon room", self.player)\
.place_locked_item(self.create_item("Weight of True Guilt"))
if not self.multiworld.ossuary_shuffle[self.player]:
self.place_items_from_dict(Vanilla.ossuary_dict)
if not self.multiworld.boss_shuffle[self.player]:
self.place_items_from_dict(Vanilla.boss_dict)
if not self.multiworld.wound_shuffle[self.player]:
self.place_items_from_dict(Vanilla.wound_dict)
if not self.multiworld.mask_shuffle[self.player]:
self.place_items_from_dict(Vanilla.mask_dict)
if not self.multiworld.eye_shuffle[self.player]:
self.place_items_from_dict(Vanilla.eye_dict)
if not self.multiworld.herb_shuffle[self.player]:
self.place_items_from_dict(Vanilla.herb_dict)
if not self.multiworld.church_shuffle[self.player]:
self.place_items_from_dict(Vanilla.church_dict)
if not self.multiworld.shop_shuffle[self.player]:
self.place_items_from_dict(Vanilla.shop_dict)
if self.multiworld.thorn_shuffle[self.player] == 2:
self.place_items_from_set(Vanilla.thorn_set, "Thorn Upgrade")
if not self.multiworld.candle_shuffle[self.player]:
self.place_items_from_dict(Vanilla.candle_dict)
if self.multiworld.start_wheel[self.player]:
self.multiworld.get_location("BotSS: Beginning gift", self.player)\
.place_locked_item(self.create_item("The Young Mason's Wheel"))
if not self.multiworld.skill_randomizer[self.player]:
self.place_items_from_dict(Vanilla.skill_dict)
if self.multiworld.thorn_shuffle[self.player] == 1:
self.multiworld.local_items[self.player].value.add("Thorn Upgrade")
def place_items_from_set(self, location_set: Set[str], name: str):
for loc in location_set:
self.multiworld.get_location(loc, self.player)\
.place_locked_item(self.create_item(name))
def place_items_from_dict(self, option_dict: Dict[str, str]):
for loc, item in option_dict.items():
self.multiworld.get_location(loc, self.player)\
.place_locked_item(self.create_item(item))
def create_regions(self) -> None:
player = self.player
world = self.multiworld
region_table: Dict[str, Region] = {
"menu" : Region("Menu", player, world),
"albero" : Region("Albero", player, world),
"attots" : Region("All the Tears of the Sea", player, world),
"ar" : Region("Archcathedral Rooftops", player, world),
"bottc" : Region("Bridge of the Three Cavalries", player, world),
"botss" : Region("Brotherhood of the Silent Sorrow", player, world),
"coolotcv": Region("Convent of Our Lady of the Charred Visage", player, world),
"dohh" : Region("Deambulatory of His Holiness", player, world),
"dc" : Region("Desecrated Cistern", player, world),
"eos" : Region("Echoes of Salt", player, world),
"ft" : Region("Ferrous Tree", player, world),
"gotp" : Region("Graveyard of the Peaks", player, world),
"ga" : Region("Grievance Ascends", player, world),
"hotd" : Region("Hall of the Dawning", player, world),
"jondo" : Region("Jondo", player, world),
"kottw" : Region("Knot of the Three Words", player, world),
"lotnw" : Region("Library of the Negated Words", player, world),
"md" : Region("Mercy Dreams", player, world),
"mom" : Region("Mother of Mothers", player, world),
"moted" : Region("Mountains of the Endless Dusk", player, world),
"mah" : Region("Mourning and Havoc", player, world),
"potss" : Region("Patio of the Silent Steps", player, world),
"petrous" : Region("Petrous", player, world),
"thl" : Region("The Holy Line", player, world),
"trpots" : Region("The Resting Place of the Sister", player, world),
"tsc" : Region("The Sleeping Canvases", player, world),
"wothp" : Region("Wall of the Holy Prohibitions", player, world),
"wotbc" : Region("Wasteland of the Buried Churches", player, world),
"wotw" : Region("Where Olive Trees Wither", player, world),
"dungeon" : Region("Dungeons", player, world)
}
for rname, reg in region_table.items():
world.regions.append(reg)
for ename, exits in region_exit_table.items():
if ename == rname:
for i in exits:
ent = Entrance(player, i, reg)
reg.exits.append(ent)
for e, r in exit_lookup_table.items():
if i == e:
ent.connect(region_table[r])
for loc in location_table:
id = base_id + location_table.index(loc)
region_table[loc["region"]].locations\
.append(BlasphemousLocation(self.player, loc["name"], id, region_table[loc["region"]]))
victory = Location(self.player, "His Holiness Escribar", None, self.multiworld.get_region("Deambulatory of His Holiness", self.player))
victory.place_locked_item(self.create_event("Victory"))
self.multiworld.get_region("Deambulatory of His Holiness", self.player).locations.append(victory)
if self.multiworld.ending[self.player].value == 1:
set_rule(victory, lambda state: state.has("Thorn Upgrade", player, 8))
elif self.multiworld.ending[self.player].value == 2:
set_rule(victory, lambda state: state.has("Thorn Upgrade", player, 8) and \
state.has("Holy Wound of Abnegation", player))
self.multiworld.completion_condition[self.player] = lambda state: state.has("Victory", self.player)
def fill_slot_data(self) -> Dict[str, Any]:
slot_data: Dict[str, Any] = {}
locations = []
for loc in self.multiworld.get_filled_locations(self.player):
if loc.name == "His Holiness Escribar":
continue
else:
data = {
"id": self.location_name_to_game_id[loc.name],
"ap_id": loc.address,
"name": loc.item.name,
"player_name": self.multiworld.player_name[loc.item.player]
}
if loc.name in shop_set:
data["type"] = loc.item.classification.name
locations.append(data)
config = {
"versionCreated": "AP",
"general": {
"teleportationAlwaysUnlocked": bool(self.multiworld.prie_dieu_warp[self.player].value),
"skipCutscenes": bool(self.multiworld.skip_cutscenes[self.player].value),
"enablePenitence": bool(self.multiworld.penitence[self.player].value),
"hardMode": False,
"customSeed": 0,
"allowHints": bool(self.multiworld.corpse_hints[self.player].value)
},
"items": {
"type": 1,
"lungDamage": False,
"disableNPCDeath": True,
"startWithWheel": bool(self.multiworld.start_wheel[self.player].value),
"shuffleReliquaries": bool(self.multiworld.reliquary_shuffle[self.player].value)
},
"enemies": {
"type": self.multiworld.enemy_randomizer[self.player].value,
"maintainClass": bool(self.multiworld.enemy_groups[self.player].value),
"areaScaling": bool(self.multiworld.enemy_scaling[self.player].value)
},
"prayers": {
"type": 0,
"removeMirabis": False
},
"doors": {
"type": 0
},
"debug": {
"type": 0
}
}
slot_data = {
"locations": locations,
"cfg": config,
"ending": self.multiworld.ending[self.player].value,
"death_link": bool(self.multiworld.death_link[self.player].value)
}
return slot_data
class BlasphemousItem(Item):
game: str = "Blasphemous"
class BlasphemousLocation(Location):
game: str = "Blasphemous"

View File

@@ -0,0 +1,64 @@
# Blasphemous
## Where is the settings page?
The [player settings page for this game](../player-settings) contains all the options you need to configure and export a config file.
## What does randomization do to this game?
All items that appear on the ground are randomized, and there are options to randomize more of the game, such as stat upgrades, enemies, skills, and more.
In addition, there are other changes to the game that make it better optimized for a randomizer:
- Some items and enemies are never randomized.
- Teleportation between Prie Dieus can be unlocked from the beginning.
- New save files are created in True Torment mode (NG+), but enemies and bosses will use their regular attack & defense values. A penitence can be chosen if the option is enabled.
- Save files can not be ascended - the randomizer is meant to be completed in a single playthrough.
- The Ossuary will give a reward for every four bones collected.
- Side quests have been modified so that the items received from them cannot be missed.
- The Apodictic Heart of Mea Culpa can be unequipped.
- Dying with the Immaculate Bead is unnecessary, it is automatically upgraded to the Weight of True Guilt.
- If the option is enabled, the 34 corpses in game will have their messages changed to give hints about certain items and locations. The Shroud of Dreamt Sins is not required to hear them.
## What has been changed about the side quests?
Tirso:
- Tirso's helpers will never die. Herbs can be given to him at any time.
Gemino:
- Gemino will never freeze. The thimble can be given to him at any time.
Viridiana:
- Viridiana will never die. The player can ask for her assistance at all 5 boss fights, and she will still appear at the rooftops.
Redento:
- No changes.
Cleofas:
- The choice to end Socorro's suffering has been removed.
- Cleofas will not jump off the rooftops, even after talking to him without the Cord of the True Burying.
Crisanta / Ending C:
- The Incomplete Scapular will not skip the fight with Esdras. Instead, it is required to open the door to the church in Brotherhood of the Silent Sorrow.
- Perpetva's item from The Resting Place of the Sister is always accessible, even after defeating Esdras.
- Crisanta's gift in Brotherhood of the Silent Sorrow will always be the Holy Wound of Abnegation.
- When fighting Crisanta, it is no longer required to have the Apodictic Heart of Mea Culpa equipped to continue with Ending C, it just needs to be in the player's inventory.
## Which items and enemies are never randomized?
Items:
- Golden Thimble Filled with Burning Oil - from the fountain of burning oil in Convent of Our Lady of the Charred Visage
- Hatched Egg of Deformity - from the tree in Mountains of the Endless Dusk
- Chalice of Inverted Verses - from the statue in Desecrated Cistern
- Holy Wound of Abnegation - given by Crisanta in Brotherhood of the Silent Sorrow
Enemies:
- The Charging Knell in Mountains of the Endless Dusk
- The bell ringer in lower east Jondo
- The first Phalaris, Lionheart, and Sleepless Tomb in their respective areas (Chalice of Inverted Verses quest)
In addition, any enemies that appear in some kind of arena (such as the Confessor Dungeons, or the bridges in Archcathedral Rooftops or Grievance Ascends) will not be randomized to prevent softlocking.
## What does another world's item look like in Blasphemous?
Items retain their original appearance. You won't know if an item is for another player until you collect it. The only exception to this is the shops, where items that belong to other players are represented by the Archipelago logo.

View File

@@ -0,0 +1,21 @@
# Blasphemous Multiworld Setup Guide
## Required Software
- Blasphemous from: [Steam](https://store.steampowered.com/app/774361/Blasphemous/)
- Blasphemous Modding API from: [GitHub](https://github.com/BrandenEK/Blasphemous-Modding-API)
- Blasphemous Randomizer from: [GitHub](https://github.com/BrandenEK/Blasphemous-Randomizer)
- Blasphemous Multiworld from: [GitHub](https://github.com/BrandenEK/Blasphemous-Multiworld)
## Instructions (Windows)
1. Download the [Modding API](https://github.com/BrandenEK/Blasphemous-Modding-API/releases), and follow the [installation instructions](https://github.com/BrandenEK/Blasphemous-Modding-API#installation) on the GitHub page.
2. After the Modding API has been installed, download the [Randomizer](https://github.com/BrandenEK/Blasphemous-Randomizer/releases) and [Multiworld](https://github.com/BrandenEK/Blasphemous-Multiworld/releases) archives, and extract the contents of both into the `Modding` folder.
3. Start Blasphemous. To verfy that the mods are working, look for a version number for both the Randomizer and Multiworld on the title screen.
## Connecting
To connect to an Archipelago server, open the in-game console by pressing backslash `\` and use the command `multiworld connect [address:port] [name] [password]`. The port and password are both optional - if no port is provided then the default port of 38281 is used.
**Make sure to connect to the server before attempting to start a new save file.**

View File

@@ -1,10 +1,18 @@
import sys
from BaseClasses import Item
from worlds.dark_souls_3.data.items_data import item_tables
from worlds.dark_souls_3.data.items_data import item_tables, dlc_shields_table, dlc_weapons_upgrade_10_table, \
dlc_weapons_upgrade_5_table, dlc_goods_table, dlc_spells_table, dlc_armor_table, dlc_ring_table, dlc_misc_table, dlc_goods_2_table
class DarkSouls3Item(Item):
game: str = "Dark Souls III"
dlc_set = {**dlc_shields_table, **dlc_weapons_upgrade_10_table, **dlc_weapons_upgrade_5_table,
**dlc_goods_table, **dlc_spells_table, **dlc_armor_table, **dlc_ring_table, **dlc_misc_table}
dlc_progressive = {**dlc_goods_2_table}
@staticmethod
def get_name_to_id() -> dict:
base_id = 100000
@@ -12,6 +20,17 @@ class DarkSouls3Item(Item):
output = {}
for i, table in enumerate(item_tables):
if len(table) > table_offset:
raise Exception("An item table has {} entries, that is more than {} entries (table #{})".format(len(table), table_offset, i))
output.update({name: id for id, name in enumerate(table, base_id + (table_offset * i))})
return output
@staticmethod
def is_dlc_item(name) -> bool:
return name in DarkSouls3Item.dlc_set
@staticmethod
def is_dlc_progressive(name) -> bool:
return name in DarkSouls3Item.dlc_progressive

View File

@@ -1,5 +1,8 @@
import sys
from BaseClasses import Location
from worlds.dark_souls_3.data.locations_data import location_tables
from worlds.dark_souls_3.data.locations_data import location_tables, painted_world_table, dreg_heap_table, \
ringed_city_table
class DarkSouls3Location(Location):
@@ -12,6 +15,8 @@ class DarkSouls3Location(Location):
output = {}
for i, table in enumerate(location_tables):
if len(table) > table_offset:
raise Exception("A location table has {} entries, that is more than {} entries (table #{})".format(len(table), table_offset, i))
output.update({name: id for id, name in enumerate(table, base_id + (table_offset * i))})
return output

View File

@@ -1,5 +1,5 @@
import typing
from Options import Toggle, Option, DeathLink
from Options import Toggle, Option, Range, Choice, DeathLink
class AutoEquipOption(Toggle):
@@ -29,10 +29,57 @@ class NoEquipLoadOption(Toggle):
display_name = "No Equip load"
class RandomizeWeaponsLevelOption(Toggle):
"""Enable this option to upgrade 33% ( based on the probability chance ) of the pool of weapons to a random value
between +1 and +5/+10"""
class RandomizeWeaponsLevelOption(Choice):
"""Enable this option to upgrade a percentage of the pool of weapons to a random value between the minimum and
maximum levels defined.
all: All weapons are eligible, both basic and epic
basic: Only weapons that can be upgraded to +10
epic: Only weapons that can be upgraded to +5"""
display_name = "Randomize weapons level"
option_none = 0
option_all = 1
option_basic = 2
option_epic = 3
class RandomizeWeaponsLevelPercentageOption(Range):
"""The percentage of weapons in the pool to be upgraded if randomize weapons level is toggled"""
display_name = "Percentage of randomized weapons"
range_start = 1
range_end = 100
default = 33
class MinLevelsIn5WeaponPoolOption(Range):
"""The minimum upgraded value of a weapon in the pool of weapons that can only reach +5"""
display_name = "Minimum level of +5 weapons"
range_start = 1
range_end = 5
default = 1
class MaxLevelsIn5WeaponPoolOption(Range):
"""The maximum upgraded value of a weapon in the pool of weapons that can only reach +5"""
display_name = "Maximum level of +5 weapons"
range_start = 1
range_end = 5
default = 5
class MinLevelsIn10WeaponPoolOption(Range):
"""The minimum upgraded value of a weapon in the pool of weapons that can reach +10"""
display_name = "Minimum level of +10 weapons"
range_start = 1
range_end = 10
default = 1
class MaxLevelsIn10WeaponPoolOption(Range):
"""The maximum upgraded value of a weapon in the pool of weapons that can reach +10"""
display_name = "Maximum level of +10 weapons"
range_start = 1
range_end = 10
default = 10
class LateBasinOfVowsOption(Toggle):
@@ -41,14 +88,31 @@ class LateBasinOfVowsOption(Toggle):
display_name = "Late Basin of Vows"
class EnableProgressiveLocationsOption(Toggle):
"""Randomize upgrade materials such as the titanite shards, the estus shards and the consumables"""
display_name = "Randomize materials, Estus shards and consumables (+196 checks/items)"
class EnableDLCOption(Toggle):
"""To use this option, you must own both the ASHES OF ARIANDEL and the RINGED CITY DLC"""
display_name = "Add the DLC Items and Locations to the pool (+81 checks/items)"
dark_souls_options: typing.Dict[str, type(Option)] = {
"auto_equip": AutoEquipOption,
"lock_equip": LockEquipOption,
"no_weapon_requirements": NoWeaponRequirementsOption,
"randomize_weapons_level": RandomizeWeaponsLevelOption,
"randomize_weapons_percentage": RandomizeWeaponsLevelPercentageOption,
"min_levels_in_5": MinLevelsIn5WeaponPoolOption,
"max_levels_in_5": MaxLevelsIn5WeaponPoolOption,
"min_levels_in_10": MinLevelsIn10WeaponPoolOption,
"max_levels_in_10": MaxLevelsIn10WeaponPoolOption,
"late_basin_of_vows": LateBasinOfVowsOption,
"no_spell_requirements": NoSpellRequirementsOption,
"no_equip_load": NoEquipLoadOption,
"death_link": DeathLink,
"enable_progressive_locations": EnableProgressiveLocationsOption,
"enable_dlc": EnableDLCOption,
}

View File

@@ -1,18 +1,18 @@
# world/dark_souls_3/__init__.py
import json
import os
from typing import Dict
from .Items import DarkSouls3Item
from .Locations import DarkSouls3Location
from .Options import dark_souls_options
from .data.items_data import weapons_upgrade_5_table, weapons_upgrade_10_table, item_dictionary, key_items_list
from .data.items_data import weapons_upgrade_5_table, weapons_upgrade_10_table, item_dictionary, key_items_list, \
dlc_weapons_upgrade_5_table, dlc_weapons_upgrade_10_table
from .data.locations_data import location_dictionary, fire_link_shrine_table, \
high_wall_of_lothric, \
undead_settlement_table, road_of_sacrifice_table, consumed_king_garden_table, cathedral_of_the_deep_table, \
farron_keep_table, catacombs_of_carthus_table, smouldering_lake_table, irithyll_of_the_boreal_valley_table, \
irithyll_dungeon_table, profaned_capital_table, anor_londo_table, lothric_castle_table, grand_archives_table, \
untended_graves_table, archdragon_peak_table, firelink_shrine_bell_tower_table, progressive_locations
untended_graves_table, archdragon_peak_table, firelink_shrine_bell_tower_table, progressive_locations, \
progressive_locations_2, progressive_locations_3, painted_world_table, dreg_heap_table, ringed_city_table, dlc_progressive_locations
from ..AutoWorld import World, WebWorld
from BaseClasses import MultiWorld, Region, Item, Entrance, Tutorial, ItemClassification
from ..generic.Rules import set_rule, add_item_rule
@@ -52,9 +52,9 @@ class DarkSouls3World(World):
option_definitions = dark_souls_options
topology_present: bool = True
web = DarkSouls3Web()
data_version = 4
data_version = 5
base_id = 100000
required_client_version = (0, 3, 6)
required_client_version = (0, 3, 7)
item_name_to_id = DarkSouls3Item.get_name_to_id()
location_name_to_id = DarkSouls3Location.get_name_to_id()
@@ -77,7 +77,15 @@ class DarkSouls3World(World):
return DarkSouls3Item(name, item_classification, data, self.player)
def create_regions(self):
menu_region = self.create_region("Menu", progressive_locations)
if self.multiworld.enable_progressive_locations[self.player].value and self.multiworld.enable_dlc[self.player].value:
menu_region = self.create_region("Menu", {**progressive_locations, **progressive_locations_2,
**progressive_locations_3, **dlc_progressive_locations})
elif self.multiworld.enable_progressive_locations[self.player].value:
menu_region = self.create_region("Menu", {**progressive_locations, **progressive_locations_2,
**progressive_locations_3})
else:
menu_region = self.create_region("Menu", None)
# Create all Vanilla regions of Dark Souls III
firelink_shrine_region = self.create_region("Firelink Shrine", fire_link_shrine_table)
@@ -101,6 +109,11 @@ class DarkSouls3World(World):
untended_graves_region = self.create_region("Untended Graves", untended_graves_table)
archdragon_peak_region = self.create_region("Archdragon Peak", archdragon_peak_table)
kiln_of_the_first_flame_region = self.create_region("Kiln Of The First Flame", None)
# DLC Down here
if self.multiworld.enable_dlc[self.player]:
painted_world_of_ariandel_region = self.create_region("Painted World of Ariandel", painted_world_table)
dreg_heap_region = self.create_region("Dreg Heap", dreg_heap_table)
ringed_city_region = self.create_region("Ringed City", ringed_city_table)
# Create the entrance to connect those regions
menu_region.exits.append(Entrance(self.player, "New Game", menu_region))
@@ -112,7 +125,8 @@ class DarkSouls3World(World):
firelink_shrine_region.exits.append(Entrance(self.player, "Goto Bell Tower",
firelink_shrine_region))
self.multiworld.get_entrance("Goto High Wall of Lothric", self.player).connect(high_wall_of_lothric_region)
self.multiworld.get_entrance("Goto Kiln Of The First Flame", self.player).connect(kiln_of_the_first_flame_region)
self.multiworld.get_entrance("Goto Kiln Of The First Flame", self.player).connect(
kiln_of_the_first_flame_region)
self.multiworld.get_entrance("Goto Bell Tower", self.player).connect(firelink_shrine_bell_tower_region)
high_wall_of_lothric_region.exits.append(Entrance(self.player, "Goto Undead Settlement",
high_wall_of_lothric_region))
@@ -133,7 +147,7 @@ class DarkSouls3World(World):
catacombs_of_carthus_region))
catacombs_of_carthus_region.exits.append(Entrance(self.player, "Goto Smouldering Lake",
catacombs_of_carthus_region))
self.multiworld.get_entrance("Goto Irithyll of the boreal", self.player).\
self.multiworld.get_entrance("Goto Irithyll of the boreal", self.player). \
connect(irithyll_of_the_boreal_valley_region)
self.multiworld.get_entrance("Goto Smouldering Lake", self.player).connect(smouldering_lake_region)
irithyll_of_the_boreal_valley_region.exits.append(Entrance(self.player, "Goto Irithyll dungeon",
@@ -153,6 +167,16 @@ class DarkSouls3World(World):
consumed_king_garden_region.exits.append(Entrance(self.player, "Goto Untended Graves",
consumed_king_garden_region))
self.multiworld.get_entrance("Goto Untended Graves", self.player).connect(untended_graves_region)
# DLC Connectors Below
if self.multiworld.enable_dlc[self.player]:
cathedral_of_the_deep_region.exits.append(Entrance(self.player, "Goto Painted World of Ariandel",
cathedral_of_the_deep_region))
self.multiworld.get_entrance("Goto Painted World of Ariandel", self.player).connect(painted_world_of_ariandel_region)
painted_world_of_ariandel_region.exits.append(Entrance(self.player, "Goto Dreg Heap",
painted_world_of_ariandel_region))
self.multiworld.get_entrance("Goto Dreg Heap", self.player).connect(dreg_heap_region)
dreg_heap_region.exits.append(Entrance(self.player, "Goto Ringed City", dreg_heap_region))
self.multiworld.get_entrance("Goto Ringed City", self.player).connect(ringed_city_region)
# For each region, add the associated locations retrieved from the corresponding location_table
def create_region(self, region_name, location_table) -> Region:
@@ -169,8 +193,18 @@ class DarkSouls3World(World):
def create_items(self):
for name, address in self.item_name_to_id.items():
# Specific items will be included in the item pool under certain conditions. See generate_basic
if name != "Basin of Vows":
self.multiworld.itempool += [self.create_item(name)]
if name == "Basin of Vows":
continue
# Do not add progressive_items ( containing "#" ) to the itempool if the option is disabled
if (not self.multiworld.enable_progressive_locations[self.player]) and "#" in name:
continue
# Do not add DLC items if the option is disabled
if (not self.multiworld.enable_dlc[self.player]) and DarkSouls3Item.is_dlc_item(name):
continue
# Do not add DLC Progressives if both options are disabled
if ((not self.multiworld.enable_progressive_locations[self.player]) or (not self.multiworld.enable_dlc[self.player])) and DarkSouls3Item.is_dlc_progressive(name):
continue
self.multiworld.itempool += [self.create_item(name)]
def generate_early(self):
pass
@@ -194,15 +228,23 @@ class DarkSouls3World(World):
lambda state: state.has("Grand Archives Key", self.player))
set_rule(self.multiworld.get_entrance("Goto Kiln Of The First Flame", self.player),
lambda state: state.has("Cinders of a Lord - Abyss Watcher", self.player) and
state.has("Cinders of a Lord - Yhorm the Giant", self.player) and
state.has("Cinders of a Lord - Aldrich", self.player) and
state.has("Cinders of a Lord - Lothric Prince", self.player))
state.has("Cinders of a Lord - Yhorm the Giant", self.player) and
state.has("Cinders of a Lord - Aldrich", self.player) and
state.has("Cinders of a Lord - Lothric Prince", self.player))
# DLC Access Rules Below
if self.multiworld.enable_dlc[self.player]:
set_rule(self.multiworld.get_entrance("Goto Painted World of Ariandel", self.player),
lambda state: state.has("Contraption Key", self.player))
set_rule(self.multiworld.get_entrance("Goto Ringed City", self.player),
lambda state: state.has("Small Envoy Banner", self.player))
# Define the access rules to some specific locations
set_rule(self.multiworld.get_location("HWL: Soul of the Dancer", self.player),
lambda state: state.has("Basin of Vows", self.player))
set_rule(self.multiworld.get_location("HWL: Greirat's Ashes", self.player),
lambda state: state.has("Cell Key", self.player))
set_rule(self.multiworld.get_location("HWL: Blue Tearstone Ring", self.player),
lambda state: state.has("Cell Key", self.player))
set_rule(self.multiworld.get_location("ID: Bellowing Dragoncrest Ring", self.player),
lambda state: state.has("Jailbreaker's Key", self.player))
set_rule(self.multiworld.get_location("ID: Prisoner Chief's Ashes", self.player),
@@ -242,17 +284,38 @@ class DarkSouls3World(World):
# Depending on the specified option, modify items hexadecimal value to add an upgrade level
item_dictionary_copy = item_dictionary.copy()
if self.multiworld.randomize_weapons_level[self.player]:
# Randomize some weapons upgrades
for name in weapons_upgrade_5_table.keys():
if self.multiworld.random.randint(0, 100) < 33:
value = self.multiworld.random.randint(1, 5)
item_dictionary_copy[name] += value
if self.multiworld.randomize_weapons_level[self.player] > 0:
# if the user made an error and set a min higher than the max we default to the max
max_5 = self.multiworld.max_levels_in_5[self.player]
min_5 = min(self.multiworld.min_levels_in_5[self.player], max_5)
max_10 = self.multiworld.max_levels_in_10[self.player]
min_10 = min(self.multiworld.min_levels_in_10[self.player], max_10)
weapons_percentage = self.multiworld.randomize_weapons_percentage[self.player]
for name in weapons_upgrade_10_table.keys():
if self.multiworld.random.randint(0, 100) < 33:
value = self.multiworld.random.randint(1, 10)
item_dictionary_copy[name] += value
# Randomize some weapons upgrades
if self.multiworld.randomize_weapons_level[self.player] in [1, 3]: # Options are either all or +5
for name in weapons_upgrade_5_table.keys():
if self.multiworld.per_slot_randoms[self.player].randint(1, 100) < weapons_percentage:
value = self.multiworld.per_slot_randoms[self.player].randint(min_5, max_5)
item_dictionary_copy[name] += value
if self.multiworld.randomize_weapons_level[self.player] in [1, 2]: # Options are either all or +10
for name in weapons_upgrade_10_table.keys():
if self.multiworld.per_slot_randoms[self.player].randint(1, 100) < weapons_percentage:
value = self.multiworld.per_slot_randoms[self.player].randint(min_10, max_10)
item_dictionary_copy[name] += value
if self.multiworld.randomize_weapons_level[self.player] in [1, 3]:
for name in dlc_weapons_upgrade_5_table.keys():
if self.multiworld.per_slot_randoms[self.player].randint(1, 100) < weapons_percentage:
value = self.multiworld.per_slot_randoms[self.player].randint(min_5, max_5)
item_dictionary_copy[name] += value
if self.multiworld.randomize_weapons_level[self.player] in [1, 2]:
for name in dlc_weapons_upgrade_10_table.keys():
if self.multiworld.per_slot_randoms[self.player].randint(1, 100) < weapons_percentage:
value = self.multiworld.per_slot_randoms[self.player].randint(min_10, max_10)
item_dictionary_copy[name] += value
# Create the mandatory lists to generate the player's output file
items_id = []
@@ -281,6 +344,7 @@ class DarkSouls3World(World):
"death_link": self.multiworld.death_link[self.player].value,
"no_spell_requirements": self.multiworld.no_spell_requirements[self.player].value,
"no_equip_load": self.multiworld.no_equip_load[self.player].value,
"enable_dlc": self.multiworld.enable_dlc[self.player].value
},
"seed": self.multiworld.seed_name, # to verify the server's multiworld
"slot": self.multiworld.player_name[self.player], # to connect to server

View File

@@ -13,7 +13,6 @@ weapons_upgrade_5_table = {
"Izalith Staff": 0x00C96A80,
"Fume Ultra Greatsword": 0x0060E4B0,
"Black Knight Sword": 0x005F5E10,
"Yorshka's Spear": 0x008C3A70,
"Smough's Great Hammer": 0x007E30B0,
"Dragonslayer Greatbow": 0x00CF8500,
@@ -25,7 +24,6 @@ weapons_upgrade_5_table = {
"Dragonslayer Spear": 0x008CAFA0,
"Caitha's Chime": 0x00CA06C0,
"Sunlight Straight Sword": 0x00203230,
"Firelink Greatsword": 0x0060BDA0,
"Hollowslayer Greatsword": 0x00604870,
"Arstor's Spear": 0x008BEC50,
@@ -37,7 +35,6 @@ weapons_upgrade_5_table = {
"Wolnir's Holy Sword": 0x005FFA50,
"Demon's Greataxe": 0x006CA480,
"Demon's Fist": 0x00A84DF0,
"Old King's Great Hammer": 0x007CF830,
"Greatsword of Judgment": 0x005E2590,
"Profaned Greatsword": 0x005E4CA0,
@@ -55,6 +52,29 @@ weapons_upgrade_5_table = {
"Irithyll Rapier": 0x002E8A10
}
dlc_weapons_upgrade_5_table = {
"Friede's Great Scythe": 0x009B55A0,
"Rose of Ariandel": 0x00B82C70,
"Demon's Scar": 0x003F04D0, # Assigned to "RC: Church Guardian Shiv"
"Frayed Blade": 0x004D35A0, # Assigned to "RC: Ritual Spear Fragment"
"Gael's Greatsword": 0x00227C20, # Assigned to "RC: Violet Wrappings"
"Repeating Crossbow": 0x00D885B0, # Assigned to "RC: Blood of the Dark Souls"
"Onyx Blade": 0x00222E00, # VILHELM FIGHT
"Earth Seeker": 0x006D8EE0,
"Quakestone Hammer": 0x007ECCF0,
"Millwood Greatbow": 0x00D85EA0,
"Valorheart": 0x00F646E0,
"Aquamarine Dagger": 0x00116520,
"Ringed Knight Straight Sword": 0x00225510,
"Ledo's Great Hammer": 0x007EF400, # INVADER FIGHT
"Ringed Knight Spear": 0x008CFDC0,
"Crucifix of the Mad King": 0x008D4BE0,
"Sacred Chime of Filianore": 0x00CCECF0,
"Preacher's Right Arm": 0x00CD1400,
"White Birch Bow": 0x00D77440,
"Ringed Knight Paired Greatswords": 0x00F69500
}
weapons_upgrade_10_table = {
"Broken Straight Sword": 0x001EF9B0,
"Deep Battle Axe": 0x0006AFA54,
@@ -73,7 +93,6 @@ weapons_upgrade_10_table = {
"Red Hilted Halberd": 0x009AB960,
"Saint's Talisman": 0x00CACA10,
"Large Club": 0x007AFC60,
"Brigand Twindaggers": 0x00F50E60,
"Butcher Knife": 0x006BE130,
"Brigand Axe": 0x006B1DE0,
@@ -104,9 +123,24 @@ weapons_upgrade_10_table = {
"Drakeblood Greatsword": 0x00609690,
"Greatlance": 0x008A8CC0,
"Sniper Crossbow": 0x00D83790,
"Claw": 0x00A7D8C0,
"Drang Twinspears": 0x00F5AAA0,
"Pyromancy Flame": 0x00CC77C0 #given/dropped by Cornyx
}
dlc_weapons_upgrade_10_table = {
"Follower Sabre": 0x003EDDC0,
"Millwood Battle Axe": 0x006D67D0,
"Follower Javelin": 0x008CD6B0,
"Crow Talons": 0x00A89C10,
"Pyromancer's Parting Flame": 0x00CC9ED0,
"Crow Quills": 0x00F66DF0,
"Follower Torch": 0x015F1AD0,
"Murky Hand Scythe": 0x00118C30,
"Herald Curved Greatsword": 0x006159E0,
"Lothric War Banner": 0x008D24D0,
"Splitleaf Greatsword": 0x009B2E90, # SHOP ITEM
"Murky Longstaff": 0x00CCC5E0,
}
shields_table = {
@@ -132,7 +166,15 @@ shields_table = {
"Golden Wing Crest Shield": 0x0143CAA0,
"Ancient Dragon Greatshield": 0x013599D0,
"Spirit Tree Crest Shield": 0x014466E0,
"Blessed Red and White Shield": 0x01343FB9,
"Blessed Red and White Shield": 0x01343FB9
}
dlc_shields_table = {
"Followers Shield": 0x0135C0E0,
"Ethereal Oak Shield": 0x01450320,
"Giant Door Shield": 0x00F5F8C0,
"Dragonhead Shield": 0x0135E7F0,
"Dragonhead Greatshield": 0x01452A30
}
goods_table = {
@@ -167,7 +209,55 @@ goods_table = {
**{"Soul of a Great Champion #"+str(i): 0x400001A4 for i in range(1, 3)},
**{"Soul of a Champion #"+str(i): 0x400001A3 for i in range(1, 5)},
**{"Soul of a Venerable Old Hand #"+str(i): 0x400001A2 for i in range(1, 5)},
**{"Soul of a Crestfallen Knight #"+str(i): 0x40000199 for i in range(1, 11)},
**{"Soul of a Crestfallen Knight #"+str(i): 0x40000199 for i in range(1, 11)}
}
goods_2_table = { # Added by Br00ty
"HWL: Gold Pine Resin #": 0x4000014B,
"US: Charcoal Pine Resin #": 0x4000014A,
"FK: Gold Pine Bundle #": 0x40000155,
"CC: Carthus Rouge #": 0x4000014F,
"ID: Pale Pine Resin #": 0x40000150,
**{"Ember #"+str(i): 0x400001F4 for i in range(1, 45)},
**{"Titanite Shard #"+str(i): 0x400003E8 for i in range(11, 16)},
**{"Large Titanite Shard #"+str(i): 0x400003E9 for i in range(11, 16)},
**{"Titanite Scale #" + str(i): 0x400003FC for i in range(1, 25)}
}
goods_3_table = { # Added by Br00ty
**{"Fading Soul #" + str(i): 0x40000190 for i in range(1, 4)},
**{"Ring of Sacrifice #"+str(i): 0x20004EF2 for i in range(1, 5)},
**{"Homeward Bone #"+str(i): 0x4000015E for i in range(1, 17)},
**{"Green Blossom #"+str(i): 0x40000104 for i in range(1, 7)},
**{"Human Pine Resin #"+str(i): 0x4000014E for i in range(1, 3)},
**{"Charcoal Pine Bundle #"+str(i): 0x40000154 for i in range(1, 3)},
**{"Rotten Pine Resin #"+str(i): 0x40000157 for i in range(1, 3)},
**{"Alluring Skull #"+str(i): 0x40000126 for i in range(1, 9)},
**{"Rusted Coin #"+str(i): 0x400001C7 for i in range(1, 3)},
**{"Rusted Gold Coin #"+str(i): 0x400001C9 for i in range(1, 3)},
**{"Titanite Chunk #"+str(i): 0x400003EA for i in range(1, 17)},
**{"Twinkling Titanite #"+str(i): 0x40000406 for i in range(1, 8)}
}
dlc_goods_table = {
"Soul of Sister Friede": 0x400002E8,
"Soul of the Demon Prince": 0x400002EA,
"Soul of Darkeater Midir": 0x400002EB,
"Soul of Slave Knight Gael": 0x400002E9
}
dlc_goods_2_table = { #71
**{"Large Soul of an Unknown Traveler $"+str(i): 0x40000194 for i in range(1, 10)},
**{"Soul of a Weary Warrior $"+str(i): 0x40000197 for i in range(1, 6)},
**{"Large Soul of a Weary Warrior $"+str(i): 0x40000198 for i in range(1, 7)},
**{"Soul of a Crestfallen Knight $"+str(i): 0x40000199 for i in range(1, 7)},
**{"Large Soul of a Crestfallen Knight $"+str(i): 0x4000019A for i in range(1, 4)},
**{"Homeward Bone $"+str(i): 0x4000015E for i in range(1, 7)},
**{"Large Titanite Shard $"+str(i): 0x400003E9 for i in range(1, 4)},
**{"Titanite Chunk $"+str(i): 0x400003EA for i in range(1, 16)},
**{"Twinkling Titanite $"+str(i): 0x40000406 for i in range(1, 6)},
**{"Rusted Coin $"+str(i): 0x400001C7 for i in range(1, 4)},
**{"Ember $"+str(i): 0x400001F4 for i in range(1, 11)}
}
armor_table = {
@@ -265,6 +355,69 @@ armor_table = {
"Outrider Knight Armor": 0x1328BB28,
"Outrider Knight Gauntlets": 0x1328BF10,
"Outrider Knight Leggings": 0x1328C2F8,
"Cornyx's Wrap": 0x11946370,
"Cornyx's Garb": 0x11945F88,
"Cornyx's Skirt": 0x11946758
}
dlc_armor_table = {
"Slave Knight Hood": 0x134EDCE0,
"Slave Knight Armor": 0x134EE0C8,
"Slave Knight Gauntlets": 0x134EE4B0,
"Slave Knight Leggings": 0x134EE898,
"Vilhelm's Helm": 0x11312D00,
"Vilhelm's Armor": 0x113130E8,
"Vilhelm's Gauntlets": 0x113134D0,
"Vilhelm's Leggings": 0x113138B8,
#"Millwood Knight Helm": 0x139B2820, # SHOP ITEM
#"Millwood Knight Armor": 0x139B2C08, # SHOP ITEM
#"Millwood Knight Gauntlets": 0x139B2FF0, # SHOP ITEM
#"Millwood Knight Leggings": 0x139B33D8, # SHOP ITEM
"Shira's Crown": 0x11C22260,
"Shira's Armor": 0x11C22648,
"Shira's Gloves": 0x11C22A30,
"Shira's Trousers": 0x11C22E18,
#"Lapp's Helm": 0x11E84800, # SHOP ITEM
#"Lapp's Armor": 0x11E84BE8, # SHOP ITEM
#"Lapp's Gauntlets": 0x11E84FD0, # SHOP ITEM
#"Lapp's Leggings": 0x11E853B8, # SHOP ITEM
#"Ringed Knight Hood": 0x13C8EEE0, # RANDOM ENEMY DROP
#"Ringed Knight Armor": 0x13C8F2C8, # RANDOM ENEMY DROP
#"Ringed Knight Gauntlets": 0x13C8F6B0, # RANDOM ENEMY DROP
#"Ringed Knight Leggings": 0x13C8FA98, # RANDOM ENEMY DROP
#"Harald Legion Armor": 0x13D83508, # RANDOM ENEMY DROP
#"Harald Legion Gauntlets": 0x13D838F0, # RANDOM ENEMY DROP
#"Harald Legion Leggings": 0x13D83CD8, # RANDOM ENEMY DROP
"Iron Dragonslayer Helm": 0x1405F7E0,
"Iron Dragonslayer Armor": 0x1405FBC8,
"Iron Dragonslayer Gauntlets": 0x1405FFB0,
"Iron Dragonslayer Leggings": 0x14060398,
"Ruin Sentinel Helm": 0x14CC5520,
"Ruin Sentinel Armor": 0x14CC5908,
"Ruin Sentinel Gauntlets": 0x14CC5CF0,
"Ruin Sentinel Leggings": 0x14CC60D8,
"Desert Pyromancer Hood": 0x14DB9760,
"Desert Pyromancer Garb": 0x14DB9B48,
"Desert Pyromancer Gloves": 0x14DB9F30,
"Desert Pyromancer Skirt": 0x14DBA318,
#"Follower Helm": 0x137CA3A0, # RANDOM ENEMY DROP
#"Follower Armor": 0x137CA788, # RANDOM ENEMY DROP
#"Follower Gloves": 0x137CAB70, # RANDOM ENEMY DROP
#"Follower Boots": 0x137CAF58, # RANDOM ENEMY DROP
#"Ordained Hood": 0x135E1F20, # SHOP ITEM
#"Ordained Dress": 0x135E2308, # SHOP ITEM
#"Ordained Trousers": 0x135E2AD8, # SHOP ITEM
"Black Witch Veil": 0x14FA1BE0,
"Black Witch Hat": 0x14EAD9A0,
"Black Witch Garb": 0x14EADD88,
"Black Witch Wrappings": 0x14EAE170,
"Black Witch Trousers": 0x14EAE558,
"White Preacher Head": 0x14153A20,
"Antiquated Plain Garb": 0x11B2E408
}
rings_table = {
@@ -314,6 +467,12 @@ rings_table = {
"Dragonscale Ring": 0x2000515E,
"Knight Slayer's Ring": 0x20005000,
"Magic Stoneplate Ring": 0x20004E66,
"Blue Tearstone Ring": 0x20004ED4 #given/dropped by Greirat
}
dlc_ring_table = {
"Havel's Ring": 0x20004E34,
"Chillbite Ring": 0x20005208
}
spells_table = {
@@ -335,7 +494,21 @@ spells_table = {
"Divine Pillars of Light": 0x4038C340,
"Great Magic Barrier": 0x40365628,
"Great Magic Shield": 0x40144F38,
"Crystal Scroll": 0x40000856,
"Crystal Scroll": 0x40000856
}
dlc_spells_table = {
#"Boulder Heave": 0x40282170, # KILN STRAY DEMON
#"Seething Chaos": 0x402896A0, # KILN DEMON PRINCES
#"Old Moonlight": 0x4014FF00, # KILN MIDIR
"Frozen Weapon": 0x401408E8,
"Snap Freeze": 0x401A90C8,
"Great Soul Dregs": 0x401879A0,
"Flame Fan": 0x40258190,
"Lightning Arrow": 0x40358B08,
"Way of White Corona": 0x403642A0,
"Projected Heal": 0x40364688,
"Floating Chaos": 0x40257DA8
}
misc_items_table = {
@@ -347,7 +520,7 @@ misc_items_table = {
"Braille Divine Tome of Carim": 0x40000847, # Shop
"Great Swamp Pyromancy Tome": 0x4000084F, # Shop
"Farron Coal ": 0x40000837, # Shop
"Paladin's Ashes": 0x4000083D, #Shop
"Paladin's Ashes": 0x4000083D, # Shop
"Deep Braille Divine Tome": 0x40000860, # Shop
"Small Doll": 0x400007D5,
"Golden Scroll": 0x4000085C,
@@ -388,6 +561,12 @@ misc_items_table = {
"Orbeck's Ashes": 0x40000840
}
dlc_misc_table = {
"Captains Ashes": 0x4000086A,
"Contraption Key": 0x4000086B, # Needed for Painted World
"Small Envoy Banner": 0x4000086C # Needed to get to Ringed City from Dreg Heap
}
key_items_list = {
"Small Lothric Banner",
"Basin of Vows",
@@ -405,8 +584,17 @@ key_items_list = {
"Prisoner Chief's Ashes",
"Old Cell Key",
"Jailer's Key Ring",
"Contraption Key",
"Small Envoy Banner"
}
item_tables = [weapons_upgrade_5_table, weapons_upgrade_10_table, shields_table, armor_table, rings_table, spells_table, misc_items_table, goods_table]
item_tables = [weapons_upgrade_5_table, weapons_upgrade_10_table, shields_table,
armor_table, rings_table, spells_table, misc_items_table, goods_table, goods_2_table, goods_3_table,
dlc_weapons_upgrade_5_table, dlc_weapons_upgrade_10_table, dlc_shields_table, dlc_goods_table,
dlc_armor_table, dlc_spells_table, dlc_ring_table, dlc_misc_table, dlc_goods_2_table]
item_dictionary = {**weapons_upgrade_5_table, **weapons_upgrade_10_table, **shields_table,
**armor_table, **rings_table, **spells_table, **misc_items_table, **goods_table, **goods_2_table,
**goods_3_table, **dlc_weapons_upgrade_5_table, **dlc_weapons_upgrade_10_table, **dlc_shields_table,
**dlc_goods_table, **dlc_armor_table, **dlc_spells_table, **dlc_ring_table, **dlc_misc_table, **dlc_goods_2_table}
item_dictionary = {**weapons_upgrade_5_table, **weapons_upgrade_10_table, **shields_table, **armor_table, **rings_table, **spells_table, **misc_items_table, **goods_table}

View File

@@ -42,6 +42,7 @@ high_wall_of_lothric = {
"HWL: Soul of the Dancer": 0x400002CA,
"HWL: Way of Blue Covenant": 0x2000274C,
"HWL: Greirat's Ashes": 0x4000083F,
"HWL: Blue Tearstone Ring": 0x20004ED4 #given/dropped by Greirat
}
undead_settlement_table = {
@@ -91,7 +92,11 @@ undead_settlement_table = {
"US: Warrior of Sunlight Covenant": 0x20002738,
"US: Blessed Red and White Shield": 0x01343FB9,
"US: Irina's Ashes": 0x40000843,
"US: Cornyx's Ashes": 0x40000841
"US: Cornyx's Ashes": 0x40000841,
"US: Cornyx's Wrap": 0x11946370,
"US: Cornyx's Garb": 0x11945F88,
"US: Cornyx's Skirt": 0x11946758,
"US: Pyromancy Flame": 0x00CC77C0 #given/dropped by Cornyx
}
road_of_sacrifice_table = {
@@ -437,6 +442,101 @@ archdragon_peak_table = {
"AP: Havel's Greatshield": 0x013376F0,
}
painted_world_table = { # DLC
"PW: Follower Javelin": 0x008CD6B0,
"PW: Frozen Weapon": 0x401408E8,
"PW: Millwood Greatbow": 0x00D85EA0,
"PW: Captains Ashes": 0x4000086A,
"PW: Millwood Battle Axe": 0x006D67D0,
"PW: Ethereal Oak Shield": 0x01450320,
"PW: Crow Quills": 0x00F66DF0,
"PW: Slave Knight Hood": 0x134EDCE0,
"PW: Slave Knight Armor": 0x134EE0C8,
"PW: Slave Knight Gauntlets": 0x134EE4B0,
"PW: Slave Knight Leggings": 0x134EE898,
"PW: Way of White Corona": 0x403642A0,
"PW: Crow Talons": 0x00A89C10,
"PW: Quakestone Hammer": 0x007ECCF0,
"PW: Earth Seeker": 0x006D8EE0,
"PW: Follower Torch": 0x015F1AD0,
"PW: Follower Shield": 0x0135C0E0,
"PW: Follower Sabre": 0x003EDDC0,
"PW: Snap Freeze": 0x401A90C8,
"PW: Floating Chaos": 0x40257DA8,
"PW: Pyromancer's Parting Flame": 0x00CC9ED0,
"PW: Vilhelm's Helm": 0x11312D00,
"PW: Vilhelm's Armor": 0x113130E8,
"PW: Vilhelm's Gauntlets": 0x113134D0,
"PW: Vilhelm's Leggings": 0x113138B8,
"PW: Vilhelm's Leggings": 0x113138B8,
"PW: Valorheart": 0x00F646E0, # GRAVETENDER FIGHT
"PW: Champions Bones": 0x40000869, # GRAVETENDER FIGHT
"PW: Onyx Blade": 0x00222E00, # VILHELM FIGHT
"PW: Soul of Sister Friede": 0x400002E8,
"PW: Titanite Slab": 0x400003EB,
"PW: Chillbite Ring": 0x20005208,
"PW: Contraption Key": 0x4000086B # VILHELM FIGHT/NEEDED TO PROGRESS THROUGH PW
}
dreg_heap_table = { # DLC
"DH: Loincloth": 0x11B2EBD8,
"DH: Aquamarine Dagger": 0x00116520,
"DH: Murky Hand Scythe": 0x00118C30,
"DH: Murky Longstaff": 0x00CCC5E0,
"DH: Great Soul Dregs": 0x401879A0,
"DH: Lothric War Banner": 0x00CCC5E0,
"DH: Projected Heal": 0x40364688,
"DH: Desert Pyromancer Hood": 0x14DB9760,
"DH: Desert Pyromancer Garb": 0x14DB9B48,
"DH: Desert Pyromancer Gloves": 0x14DB9F30,
"DH: Desert Pyromancer Skirt": 0x14DBA318,
"DH: Giant Door Shield": 0x00F5F8C0,
"DH: Herald Curved Greatsword": 0x006159E0,
"DH: Flame Fan": 0x40258190,
"DH: Soul of the Demon Prince": 0x400002EA,
"DH: Small Envoy Banner": 0x4000086C # NEEDED TO TRAVEL TO RINGED CITY
}
ringed_city_table = { # DLC
"RC: Ruin Sentinel Helm": 0x14CC5520,
"RC: Ruin Sentinel Armor": 0x14CC5908,
"RC: Ruin Sentinel Gauntlets": 0x14CC5CF0,
"RC: Ruin Sentinel Leggings": 0x14CC60D8,
"RC: Black Witch Veil": 0x14FA1BE0,
"RC: Black Witch Hat": 0x14EAD9A0,
"RC: Black Witch Garb": 0x14EADD88,
"RC: Black Witch Wrappings": 0x14EAE170,
"RC: Black Witch Trousers": 0x14EAE558,
"RC: White Preacher Head": 0x14153A20,
"RC: Havel's Ring": 0x20004E34,
"RC: Ringed Knight Spear": 0x008CFDC0,
"RC: Dragonhead Shield": 0x0135E7F0,
"RC: Ringed Knight Straight Sword": 0x00225510,
"RC: Preacher's Right Arm": 0x00CD1400,
"RC: White Birch Bow": 0x00D77440,
"RC: Church Guardian Shiv": 0x4000013B, # Assigned to "Demon's Scar"
"RC: Dragonhead Greatshield": 0x01452A30,
"RC: Ringed Knight Paired Greatswords": 0x00F69500,
"RC: Shira's Crown": 0x11C22260,
"RC: Shira's Armor": 0x11C22648,
"RC: Shira's Gloves": 0x11C22A30,
"RC: Shira's Trousers": 0x11C22E18,
"RC: Titanite Slab": 0x400003EB, # SHIRA DROP
"RC: Crucifix of the Mad King": 0x008D4BE0, # SHIRA DROP
"RC: Sacred Chime of Filianore": 0x00CCECF0, # SHIRA DROP
"RC: Iron Dragonslayer Helm": 0x1405F7E0,
"RC: Iron Dragonslayer Armor": 0x1405FBC8,
"RC: Iron Dragonslayer Gauntlets": 0x1405FFB0,
"RC: Iron Dragonslayer Leggings": 0x14060398,
"RC: Lightning Arrow": 0x40358B08,
"RC: Ritual Spear Fragment": 0x4000028A, # Assigned to "Frayed Blade"
"RC: Antiquated Plain Garb": 0x11B2E408,
"RC: Violet Wrappings": 0x11B2E7F0, # Assigned to "Gael's Greatsword"
"RC: Soul of Darkeater Midir": 0x400002EB,
"RC: Soul of Slave Knight Gael": 0x400002E9,
"RC: Blood of the Dark Souls": 0x4000086E, # Assigned to "Repeating Crossbow"
}
progressive_locations = {
# Upgrade materials
**{"Titanite Shard #"+str(i): 0x400003E8 for i in range(1, 11)},
@@ -456,15 +556,60 @@ progressive_locations = {
**{"Soul of a Deserted Corpse #" + str(i): 0x40000191 for i in range(1, 6)},
**{"Large Soul of a Deserted Corpse #" + str(i): 0x40000192 for i in range(1, 6)},
**{"Soul of an Unknown Traveler #" + str(i): 0x40000193 for i in range(1, 6)},
**{"Large Soul of an Unknown Traveler #" + str(i): 0x40000194 for i in range(1, 6)},
**{"Large Soul of an Unknown Traveler #" + str(i): 0x40000194 for i in range(1, 6)}
}
progressive_locations_2 = {
##Added by Br00ty
"HWL: Gold Pine Resin #": 0x4000014B,
"US: Charcoal Pine Resin #": 0x4000014A,
"FK: Gold Pine Bundle #": 0x40000155,
"CC: Carthus Rouge #": 0x4000014F,
"ID: Pale Pine Resin #": 0x40000150,
**{"Titanite Scale #" + str(i): 0x400003FC for i in range(1, 27)},
**{"Fading Soul #" + str(i): 0x40000190 for i in range(1, 4)},
**{"Ring of Sacrifice #"+str(i): 0x20004EF2 for i in range(1, 5)},
**{"Homeward Bone #"+str(i): 0x4000015E for i in range(1, 17)},
**{"Ember #"+str(i): 0x400001F4 for i in range(1, 46)},
}
progressive_locations_3 = {
**{"Green Blossom #" + str(i): 0x40000104 for i in range(1, 7)},
**{"Human Pine Resin #" + str(i): 0x4000014E for i in range(1, 3)},
**{"Charcoal Pine Bundle #" + str(i): 0x40000154 for i in range(1, 3)},
**{"Rotten Pine Resin #" + str(i): 0x40000157 for i in range(1, 3)},
**{"Pale Tongue #" + str(i): 0x40000175 for i in range(1, 3)},
**{"Alluring Skull #" + str(i): 0x40000126 for i in range(1, 3)},
**{"Undead Hunter Charm #" + str(i): 0x40000128 for i in range(1, 3)},
**{"Duel Charm #" + str(i): 0x40000130 for i in range(1, 3)},
**{"Rusted Coin #" + str(i): 0x400001C7 for i in range(1, 3)},
**{"Rusted Gold Coin #" + str(i): 0x400001C9 for i in range(1, 4)},
**{"Titanite Chunk #"+str(i): 0x400003EA for i in range(1, 17)},
**{"Twinkling Titanite #"+str(i): 0x40000406 for i in range(1, 8)}
}
dlc_progressive_locations = { #71
**{"Large Soul of an Unknown Traveler $"+str(i): 0x40000194 for i in range(1, 10)},
**{"Soul of a Weary Warrior $"+str(i): 0x40000197 for i in range(1, 6)},
**{"Large Soul of a Weary Warrior $"+str(i): 0x40000198 for i in range(1, 7)},
**{"Soul of a Crestfallen Knight $"+str(i): 0x40000199 for i in range(1, 7)},
**{"Large Soul of a Crestfallen Knight $"+str(i): 0x4000019A for i in range(1, 4)},
**{"Homeward Bone $"+str(i): 0x4000015E for i in range(1, 7)},
**{"Large Titanite Shard $"+str(i): 0x400003E9 for i in range(1, 4)},
**{"Titanite Chunk $"+str(i): 0x400003EA for i in range(1, 16)},
**{"Twinkling Titanite $"+str(i): 0x40000406 for i in range(1, 6)},
**{"Rusted Coin $"+str(i): 0x400001C7 for i in range(1, 4)},
**{"Ember $"+str(i): 0x400001F4 for i in range(1, 11)}
}
location_tables = [fire_link_shrine_table, firelink_shrine_bell_tower_table, high_wall_of_lothric, undead_settlement_table, road_of_sacrifice_table,
cathedral_of_the_deep_table, farron_keep_table, catacombs_of_carthus_table, smouldering_lake_table, irithyll_of_the_boreal_valley_table,
irithyll_dungeon_table, profaned_capital_table, anor_londo_table, lothric_castle_table, consumed_king_garden_table,
grand_archives_table, untended_graves_table, archdragon_peak_table, progressive_locations]
grand_archives_table, untended_graves_table, archdragon_peak_table, progressive_locations, progressive_locations_2, progressive_locations_3,
painted_world_table, dreg_heap_table, ringed_city_table, dlc_progressive_locations]
location_dictionary = {**fire_link_shrine_table, **firelink_shrine_bell_tower_table, **high_wall_of_lothric, **undead_settlement_table, **road_of_sacrifice_table,
**cathedral_of_the_deep_table, **farron_keep_table, **catacombs_of_carthus_table, **smouldering_lake_table, **irithyll_of_the_boreal_valley_table,
**irithyll_dungeon_table, **profaned_capital_table, **anor_londo_table, **lothric_castle_table, **consumed_king_garden_table,
**grand_archives_table, **untended_graves_table, **archdragon_peak_table, **progressive_locations}
**grand_archives_table, **untended_graves_table, **archdragon_peak_table, **progressive_locations, **progressive_locations_2, **progressive_locations_3,
**painted_world_table, **dreg_heap_table, **ringed_city_table, **dlc_progressive_locations}

View File

@@ -7,19 +7,20 @@ config file.
## What does randomization do to this game?
In Dark Souls III, all unique items you can earn from a static corpse, a chest or the death of a Boss/NPC are randomized.
This exclude the upgrade materials such as the titanite shards, the estus shards and the consumables which remain at
the same location. I also added an option available from the settings page to randomize the level of the generated
weapons(from +0 to +10/+5)
In Dark Souls III, all unique items you can earn from a static corpse, a chest or the death of a Boss/NPC are
randomized.
An option is available from the settings page to also randomize the upgrade materials, the Estus shards and the
consumables.
Another option is available to randomize the level of the generated weapons(from +0 to +10/+5)
To beat the game you need to collect the 4 "Cinders of a Lord" randomized in the multiworld
To beat the game you need to collect the 4 "Cinders of a Lord" randomized in the multiworld
and kill the final boss "Soul of Cinder"
## What Dark Souls III items can appear in other players' worlds?
Every unique items from Dark Souls III can appear in other player's worlds, such as a piece of armor, an upgraded weapon
Every unique item from Dark Souls III can appear in other player's worlds, such as a piece of armor, an upgraded weapon,
or a key item.
## What does another world's item look like in Dark Souls III?
In Dark Souls III, items which need to be sent to other worlds appear as a Prism Stone.
In Dark Souls III, items which need to be sent to other worlds appear as a Prism Stone.

View File

@@ -5,6 +5,10 @@
- [Dark Souls III](https://store.steampowered.com/app/374320/DARK_SOULS_III/)
- [Dark Souls III AP Client](https://github.com/Marechal-L/Dark-Souls-III-Archipelago-client/releases)
## Optional Software
- [Dark Souls III Maptracker Pack](https://github.com/Br00ty/DS3_AP_Maptracker/releases/latest), for use with [Poptracker](https://github.com/black-sliver/PopTracker/releases)
## General Concept
The Dark Souls III AP Client is a dinput8.dll triggered when launching Dark Souls III. This .dll file will launch a command

View File

@@ -1,14 +1,20 @@
from enum import Enum
from enum import IntEnum
from typing import TypedDict
from Options import DefaultOnToggle, Range, Choice
class LocationBalancingMode(Enum):
class LocationBalancingMode(IntEnum):
disabled = 0
compromise = 1
full = 2
class DeathLinkMode(IntEnum):
disabled = 0
death_only = 1
death_and_overcook = 2
class OC2OnToggle(DefaultOnToggle):
@property
def result(self) -> bool:
@@ -31,6 +37,23 @@ class LocationBalancing(Choice):
default = LocationBalancingMode.compromise.value
class DeathLink(Choice):
"""DeathLink is an opt-in feature for Multiworlds where individual death events are propogated to all games with DeathLink enabled.
- Disabled: Death will behave as it does in the original game.
- Death Only: A DeathLink broadcast will be sent every time a chef falls into a stage hazard. All local chefs will be killed when any one perishes.
- Death and Overcook: Same as above, but an additional broadcast will be sent whenever the kitchen catches on fire from burnt food.
"""
auto_display_name = True
display_name = "DeathLink"
option_disabled = DeathLinkMode.disabled.value
option_death_only = DeathLinkMode.death_only.value
option_death_and_overcook = DeathLinkMode.death_and_overcook.value
default = DeathLinkMode.disabled.value
class AlwaysServeOldestOrder(OC2OnToggle):
"""Modifies the game so that serving an expired order doesn't target the ticket with the highest tip. This helps
players dig out of a broken tip combo faster."""
@@ -131,6 +154,9 @@ overcooked_options = {
# generator options
"location_balancing": LocationBalancing,
# deathlink
"deathlink": DeathLink,
# randomization options
"shuffle_level_order": ShuffleLevelOrder,
"include_horde_levels": IncludeHordeLevels,

View File

@@ -1,4 +1,4 @@
from enum import Enum
from enum import Enum, IntEnum
from typing import List
@@ -113,7 +113,7 @@ ITEMS_TO_EXCLUDE_IF_NO_DLC = [
"Calmer Unbread",
]
class Overcooked2GameWorld(Enum):
class Overcooked2GameWorld(IntEnum):
ONE = 1
TWO = 2
THREE = 3
@@ -127,7 +127,7 @@ class Overcooked2GameWorld(Enum):
if self == Overcooked2GameWorld.KEVIN:
return "Kevin"
return str(int(self.value))
return str(self.value)
@property
def sublevel_count(self) -> int:
@@ -141,7 +141,7 @@ class Overcooked2GameWorld(Enum):
if self == Overcooked2GameWorld.ONE:
return 1
prev = Overcooked2GameWorld(self.value - 1)
prev = Overcooked2GameWorld(self - 1)
return prev.base_id + prev.sublevel_count
@property
@@ -195,7 +195,7 @@ class Overcooked2Level:
if self.sublevel > self.world.sublevel_count:
if self.world == Overcooked2GameWorld.KEVIN:
raise StopIteration
self.world = Overcooked2GameWorld(self.world.value + 1)
self.world = Overcooked2GameWorld(self.world + 1)
self.sublevel = 1
return self

View File

@@ -1,4 +1,4 @@
from enum import Enum
from enum import IntEnum
from typing import Callable, Dict, Any, List, Optional
from BaseClasses import ItemClassification, CollectionState, Region, Entrance, Location, Tutorial, LocationProgressType
@@ -6,7 +6,7 @@ from worlds.AutoWorld import World, WebWorld
from .Overcooked2Levels import Overcooked2Level, Overcooked2GenericLevel, ITEMS_TO_EXCLUDE_IF_NO_DLC
from .Locations import Overcooked2Location, oc2_location_name_to_id, oc2_location_id_to_name
from .Options import overcooked_options, OC2Options, OC2OnToggle, LocationBalancingMode
from .Options import overcooked_options, OC2Options, OC2OnToggle, LocationBalancingMode, DeathLinkMode
from .Items import item_table, Overcooked2Item, item_name_to_id, item_id_to_name, item_to_unlock_event, item_frequencies
from .Logic import has_requirements_for_level_star, has_requirements_for_level_access, level_shuffle_factory, is_item_progression, is_useful
@@ -27,7 +27,7 @@ class Overcooked2Web(WebWorld):
tutorials = [setup_en]
class PrepLevelMode(Enum):
class PrepLevelMode(IntEnum):
original = 0
excluded = 1
ayce = 2
@@ -179,7 +179,7 @@ class Overcooked2World(World):
balancing_mode = self.get_options()["LocationBalancing"]
if balancing_mode == LocationBalancingMode.disabled.value:
if balancing_mode == LocationBalancingMode.disabled:
# Location balancing is disabled, progression density is purely determined by filler
return list()
@@ -191,12 +191,12 @@ class Overcooked2World(World):
game_progression_count += 1
game_progression_density = game_progression_count/game_item_count
if balancing_mode == LocationBalancingMode.full.value:
if balancing_mode == LocationBalancingMode.full:
# Location balancing will be employed in an attempt to keep the number of
# progression locations and proression items as close to equal as possible
return self.get_n_random_locations(game_progression_count)
assert balancing_mode == LocationBalancingMode.compromise.value
assert balancing_mode == LocationBalancingMode.compromise
# Count how many progression items are shuffled between all games
total_item_count = len(self.multiworld.itempool)
@@ -242,7 +242,7 @@ class Overcooked2World(World):
self.level_mapping = \
level_shuffle_factory(
self.multiworld.random,
self.options["PrepLevels"] != PrepLevelMode.excluded.value,
self.options["PrepLevels"] != PrepLevelMode.excluded,
self.options["IncludeHordeLevels"],
)
else:
@@ -508,6 +508,8 @@ class Overcooked2World(World):
"SaveFolderName": mod_name,
"CustomOrderTimeoutPenalty": 10,
"LevelForceHide": [37, 38, 39, 40, 41, 42, 43, 44],
"LocalDeathLink": self.options["DeathLink"] != DeathLinkMode.disabled,
"BurnTriggersDeath": self.options["DeathLink"] == DeathLinkMode.death_and_overcook,
# Game Modifications
"LevelPurchaseRequirements": level_purchase_requirements,
@@ -560,7 +562,7 @@ class Overcooked2World(World):
for bug in bugs:
self.options[bug] = self.options["FixBugs"]
self.options["PreserveCookingProgress"] = self.options["AlwaysPreserveCookingProgress"]
self.options["TimerAlwaysStarts"] = self.options["PrepLevels"] == PrepLevelMode.ayce.value
self.options["TimerAlwaysStarts"] = self.options["PrepLevels"] == PrepLevelMode.ayce
self.options["LevelTimerScale"] = 0.666 if self.options["ShorterLevelDuration"] else 1.0
self.options["LeaderboardScoreScale"] = {
"FourStars": 1.0,

View File

@@ -0,0 +1,188 @@
from typing import Dict, Any, Iterable, Optional, Union
from BaseClasses import Region, Entrance, Location, Item, Tutorial
from worlds.AutoWorld import World, WebWorld
from . import rules, logic, options
from .bundles import get_all_bundles, Bundle
from .items import item_table, create_items, ItemData, Group
from .locations import location_table, create_locations, LocationData
from .logic import StardewLogic, StardewRule, _True, _And
from .options import stardew_valley_options, StardewOptions, fetch_options
from .regions import create_regions
from .rules import set_rules
client_version = 0
class StardewLocation(Location):
game: str = "Stardew Valley"
def __init__(self, player: int, name: str, address: Optional[int], parent=None):
super().__init__(player, name, address, parent)
self.event = not address
class StardewItem(Item):
game: str = "Stardew Valley"
class StardewWebWorld(WebWorld):
theme = "dirt"
bug_report_page = "https://github.com/agilbert1412/StardewArchipelago/issues/new?labels=bug&title=%5BBug%5D%3A+Brief+Description+of+bug+here"
tutorials = [Tutorial(
"Multiworld Setup Guide",
"A guide to playing Stardew Valley with Archipelago.",
"English",
"setup_en.md",
"setup/en",
["KaitoKid", "Jouramie"]
)]
class StardewValleyWorld(World):
"""
Stardew Valley farming simulator game where the objective is basically to spend the least possible time on your farm.
"""
game = "Stardew Valley"
option_definitions = stardew_valley_options
topology_present = False
item_name_to_id = {name: data.code for name, data in item_table.items()}
location_name_to_id = {name: data.code for name, data in location_table.items()}
data_version = 1
required_client_version = (0, 3, 9)
options: StardewOptions
logic: StardewLogic
web = StardewWebWorld()
modified_bundles: Dict[str, Bundle]
randomized_entrances: Dict[str, str]
def generate_early(self):
self.options = fetch_options(self.multiworld, self.player)
self.logic = StardewLogic(self.player, self.options)
self.modified_bundles = get_all_bundles(self.multiworld.random,
self.logic,
self.options[options.BundleRandomization],
self.options[options.BundlePrice])
def create_regions(self):
def create_region(name: str, exits: Iterable[str]) -> Region:
region = Region(name, self.player, self.multiworld)
region.exits = [Entrance(self.player, exit_name, region) for exit_name in exits]
return region
world_regions, self.randomized_entrances = create_regions(create_region, self.multiworld.random, self.options)
self.multiworld.regions.extend(world_regions)
def add_location(name: str, code: Optional[int], region: str):
region = self.multiworld.get_region(region, self.player)
location = StardewLocation(self.player, name, code, region)
location.access_rule = lambda _: True
region.locations.append(location)
create_locations(add_location, self.options, self.multiworld.random)
def create_items(self):
locations_count = len([location
for location in self.multiworld.get_locations(self.player)
if not location.event])
items_to_exclude = [excluded_items
for excluded_items in self.multiworld.precollected_items[self.player]
if not item_table[excluded_items.name].has_any_group(Group.RESOURCE_PACK,
Group.FRIENDSHIP_PACK)]
created_items = create_items(self.create_item, locations_count + len(items_to_exclude), self.options,
self.multiworld.random)
self.multiworld.itempool += created_items
for item in items_to_exclude:
self.multiworld.itempool.remove(item)
self.setup_season_events()
self.setup_victory()
def set_rules(self):
set_rules(self.multiworld, self.player, self.options, self.logic, self.modified_bundles)
def create_item(self, item: Union[str, ItemData]) -> StardewItem:
if isinstance(item, str):
item = item_table[item]
return StardewItem(item.name, item.classification, item.code, self.player)
def setup_season_events(self):
self.multiworld.push_precollected(self.create_item("Spring"))
self.create_event_location(location_table["Summer"], self.logic.received("Spring"), "Summer")
self.create_event_location(location_table["Fall"], self.logic.received("Summer"), "Fall")
self.create_event_location(location_table["Winter"], self.logic.received("Fall"), "Winter")
self.create_event_location(location_table["Year Two"], self.logic.received("Winter"), "Year Two")
def setup_victory(self):
if self.options[options.Goal] == options.Goal.option_community_center:
self.create_event_location(location_table["Complete Community Center"],
self.logic.can_complete_community_center().simplify(),
"Victory")
elif self.options[options.Goal] == options.Goal.option_grandpa_evaluation:
self.create_event_location(location_table["Succeed Grandpa's Evaluation"],
self.logic.can_finish_grandpa_evaluation().simplify(),
"Victory")
elif self.options[options.Goal] == options.Goal.option_bottom_of_the_mines:
self.create_event_location(location_table["Reach the Bottom of The Mines"],
self.logic.can_mine_to_floor(120).simplify(),
"Victory")
elif self.options[options.Goal] == options.Goal.option_cryptic_note:
self.create_event_location(location_table["Complete Quest Cryptic Note"],
self.logic.can_complete_quest("Cryptic Note").simplify(),
"Victory")
elif self.options[options.Goal] == options.Goal.option_master_angler:
self.create_event_location(location_table["Catch Every Fish"],
self.logic.can_catch_every_fish().simplify(),
"Victory")
self.multiworld.completion_condition[self.player] = lambda state: state.has("Victory", self.player)
def create_event_location(self, location_data: LocationData, rule: StardewRule, item: str):
region = self.multiworld.get_region(location_data.region, self.player)
location = StardewLocation(self.player, location_data.name, None, region)
location.access_rule = rule
region.locations.append(location)
location.place_locked_item(self.create_item(item))
def get_filler_item_name(self) -> str:
return "Joja Cola"
def fill_slot_data(self) -> Dict[str, Any]:
modified_bundles = {}
for bundle_key in self.modified_bundles:
key, value = self.modified_bundles[bundle_key].to_pair()
modified_bundles[key] = value
return {
"starting_money": self.options[options.StartingMoney],
"entrance_randomization": self.options[options.EntranceRandomization],
"backpack_progression": self.options[options.BackpackProgression],
"tool_progression": self.options[options.ToolProgression],
"elevator_progression": self.options[options.TheMinesElevatorsProgression],
"skill_progression": self.options[options.SkillProgression],
"building_progression": self.options[options.BuildingProgression],
"arcade_machine_progression": self.options[options.ArcadeMachineLocations],
"help_wanted_locations": self.options[options.HelpWantedLocations],
"fishsanity": self.options[options.Fishsanity],
"death_link": self.options["death_link"],
"goal": self.options[options.Goal],
"seed": self.multiworld.per_slot_randoms[self.player].randrange(1000000000), # Seed should be max 9 digits
"multiple_day_sleep_enabled": self.options[options.MultipleDaySleepEnabled],
"multiple_day_sleep_cost": self.options[options.MultipleDaySleepCost],
"experience_multiplier": self.options[options.ExperienceMultiplier],
"debris_multiplier": self.options[options.DebrisMultiplier],
"quick_start": self.options[options.QuickStart],
"gifting": self.options[options.Gifting],
"gift_tax": self.options[options.GiftTax],
"modified_bundles": modified_bundles,
"randomized_entrances": self.randomized_entrances,
"client_version": "2.2.2",
}

View File

@@ -0,0 +1,414 @@
from dataclasses import dataclass
from . import fish_data
from .game_item import GameItem
quality_dict = {
0: "",
1: "Silver",
2: "Gold",
3: "Iridium"
}
@dataclass(frozen=True)
class BundleItem:
item: GameItem
amount: int
quality: int
@staticmethod
def item_bundle(name: str, item_id: int, amount: int, quality: int):
return BundleItem(GameItem(name, item_id), amount, quality)
@staticmethod
def money_bundle(amount: int):
return BundleItem.item_bundle("Money", -1, amount, amount)
def as_amount(self, amount: int):
return BundleItem.item_bundle(self.item.name, self.item.item_id, amount, self.quality)
def as_quality(self, quality: int):
return BundleItem.item_bundle(self.item.name, self.item.item_id, self.amount, quality)
def __repr__(self):
return f"{self.amount} {quality_dict[self.quality]} {self.item.name}"
def __lt__(self, other):
return self.item < other.item
wild_horseradish = BundleItem.item_bundle("Wild Horseradish", 16, 1, 0)
daffodil = BundleItem.item_bundle("Daffodil", 18, 1, 0)
leek = BundleItem.item_bundle("Leek", 20, 1, 0)
dandelion = BundleItem.item_bundle("Dandelion", 22, 1, 0)
morel = BundleItem.item_bundle("Morel", 257, 1, 0)
common_mushroom = BundleItem.item_bundle("Common Mushroom", 404, 1, 0)
salmonberry = BundleItem.item_bundle("Salmonberry", 296, 1, 0)
spring_onion = BundleItem.item_bundle("Spring Onion", 399, 1, 0)
grape = BundleItem.item_bundle("Grape", 398, 1, 0)
spice_berry = BundleItem.item_bundle("Spice Berry", 396, 1, 0)
sweet_pea = BundleItem.item_bundle("Sweet Pea", 402, 1, 0)
red_mushroom = BundleItem.item_bundle("Red Mushroom", 420, 1, 0)
fiddlehead_fern = BundleItem.item_bundle("Fiddlehead Fern", 259, 1, 0)
wild_plum = BundleItem.item_bundle("Wild Plum", 406, 1, 0)
hazelnut = BundleItem.item_bundle("Hazelnut", 408, 1, 0)
blackberry = BundleItem.item_bundle("Blackberry", 410, 1, 0)
chanterelle = BundleItem.item_bundle("Chanterelle", 281, 1, 0)
winter_root = BundleItem.item_bundle("Winter Root", 412, 1, 0)
crystal_fruit = BundleItem.item_bundle("Crystal Fruit", 414, 1, 0)
snow_yam = BundleItem.item_bundle("Snow Yam", 416, 1, 0)
crocus = BundleItem.item_bundle("Crocus", 418, 1, 0)
holly = BundleItem.item_bundle("Holly", 283, 1, 0)
coconut = BundleItem.item_bundle("Coconut", 88, 1, 0)
cactus_fruit = BundleItem.item_bundle("Cactus Fruit", 90, 1, 0)
cave_carrot = BundleItem.item_bundle("Cave Carrot", 78, 1, 0)
purple_mushroom = BundleItem.item_bundle("Purple Mushroom", 422, 1, 0)
maple_syrup = BundleItem.item_bundle("Maple Syrup", 724, 1, 0)
oak_resin = BundleItem.item_bundle("Oak Resin", 725, 1, 0)
pine_tar = BundleItem.item_bundle("Pine Tar", 726, 1, 0)
nautilus_shell = BundleItem.item_bundle("Nautilus Shell", 392, 1, 0)
coral = BundleItem.item_bundle("Coral", 393, 1, 0)
sea_urchin = BundleItem.item_bundle("Sea Urchin", 397, 1, 0)
rainbow_shell = BundleItem.item_bundle("Rainbow Shell", 394, 1, 0)
clam = BundleItem(fish_data.clam, 1, 0)
cockle = BundleItem(fish_data.cockle, 1, 0)
mussel = BundleItem(fish_data.mussel, 1, 0)
oyster = BundleItem(fish_data.oyster, 1, 0)
seaweed = BundleItem.item_bundle("Seaweed", 152, 1, 0)
wood = BundleItem.item_bundle("Wood", 388, 99, 0)
stone = BundleItem.item_bundle("Stone", 390, 99, 0)
hardwood = BundleItem.item_bundle("Hardwood", 709, 10, 0)
clay = BundleItem.item_bundle("Clay", 330, 10, 0)
fiber = BundleItem.item_bundle("Fiber", 771, 99, 0)
blue_jazz = BundleItem.item_bundle("Blue Jazz", 597, 1, 0)
cauliflower = BundleItem.item_bundle("Cauliflower", 190, 1, 0)
green_bean = BundleItem.item_bundle("Green Bean", 188, 1, 0)
kale = BundleItem.item_bundle("Kale", 250, 1, 0)
parsnip = BundleItem.item_bundle("Parsnip", 24, 1, 0)
potato = BundleItem.item_bundle("Potato", 192, 1, 0)
strawberry = BundleItem.item_bundle("Strawberry", 400, 1, 0)
tulip = BundleItem.item_bundle("Tulip", 591, 1, 0)
unmilled_rice = BundleItem.item_bundle("Unmilled Rice", 271, 1, 0)
blueberry = BundleItem.item_bundle("Blueberry", 258, 1, 0)
corn = BundleItem.item_bundle("Corn", 270, 1, 0)
hops = BundleItem.item_bundle("Hops", 304, 1, 0)
hot_pepper = BundleItem.item_bundle("Hot Pepper", 260, 1, 0)
melon = BundleItem.item_bundle("Melon", 254, 1, 0)
poppy = BundleItem.item_bundle("Poppy", 376, 1, 0)
radish = BundleItem.item_bundle("Radish", 264, 1, 0)
summer_spangle = BundleItem.item_bundle("Summer Spangle", 593, 1, 0)
sunflower = BundleItem.item_bundle("Sunflower", 421, 1, 0)
tomato = BundleItem.item_bundle("Tomato", 256, 1, 0)
wheat = BundleItem.item_bundle("Wheat", 262, 1, 0)
hay = BundleItem.item_bundle("Hay", 178, 1, 0)
amaranth = BundleItem.item_bundle("Amaranth", 300, 1, 0)
bok_choy = BundleItem.item_bundle("Bok Choy", 278, 1, 0)
cranberries = BundleItem.item_bundle("Cranberries", 282, 1, 0)
eggplant = BundleItem.item_bundle("Eggplant", 272, 1, 0)
fairy_rose = BundleItem.item_bundle("Fairy Rose", 595, 1, 0)
pumpkin = BundleItem.item_bundle("Pumpkin", 276, 1, 0)
yam = BundleItem.item_bundle("Yam", 280, 1, 0)
sweet_gem_berry = BundleItem.item_bundle("Sweet Gem Berry", 417, 1, 0)
rhubarb = BundleItem.item_bundle("Rhubarb", 252, 1, 0)
beet = BundleItem.item_bundle("Beet", 284, 1, 0)
red_cabbage = BundleItem.item_bundle("Red Cabbage", 266, 1, 0)
artichoke = BundleItem.item_bundle("Artichoke", 274, 1, 0)
egg = BundleItem.item_bundle("Egg", 176, 1, 0)
large_egg = BundleItem.item_bundle("Large Egg", 174, 1, 0)
brown_egg = BundleItem.item_bundle("Egg (Brown)", 180, 1, 0)
large_brown_egg = BundleItem.item_bundle("Large Egg (Brown)", 182, 1, 0)
wool = BundleItem.item_bundle("Wool", 440, 1, 0)
milk = BundleItem.item_bundle("Milk", 184, 1, 0)
large_milk = BundleItem.item_bundle("Large Milk", 186, 1, 0)
goat_milk = BundleItem.item_bundle("Goat Milk", 436, 1, 0)
large_goat_milk = BundleItem.item_bundle("Large Goat Milk", 438, 1, 0)
truffle = BundleItem.item_bundle("Truffle", 430, 1, 0)
duck_feather = BundleItem.item_bundle("Duck Feather", 444, 1, 0)
duck_egg = BundleItem.item_bundle("Duck Egg", 442, 1, 0)
rabbit_foot = BundleItem.item_bundle("Rabbit's Foot", 446, 1, 0)
truffle_oil = BundleItem.item_bundle("Truffle Oil", 432, 1, 0)
cloth = BundleItem.item_bundle("Cloth", 428, 1, 0)
goat_cheese = BundleItem.item_bundle("Goat Cheese", 426, 1, 0)
cheese = BundleItem.item_bundle("Cheese", 424, 1, 0)
honey = BundleItem.item_bundle("Honey", 340, 1, 0)
beer = BundleItem.item_bundle("Beer", 346, 1, 0)
juice = BundleItem.item_bundle("Juice", 350, 1, 0)
mead = BundleItem.item_bundle("Mead", 459, 1, 0)
pale_ale = BundleItem.item_bundle("Pale Ale", 303, 1, 0)
wine = BundleItem.item_bundle("Wine", 348, 1, 0)
jelly = BundleItem.item_bundle("Jelly", 344, 1, 0)
pickles = BundleItem.item_bundle("Pickles", 342, 1, 0)
caviar = BundleItem.item_bundle("Caviar", 445, 1, 0)
aged_roe = BundleItem.item_bundle("Aged Roe", 447, 1, 0)
apple = BundleItem.item_bundle("Apple", 613, 1, 0)
apricot = BundleItem.item_bundle("Apricot", 634, 1, 0)
orange = BundleItem.item_bundle("Orange", 635, 1, 0)
peach = BundleItem.item_bundle("Peach", 636, 1, 0)
pomegranate = BundleItem.item_bundle("Pomegranate", 637, 1, 0)
cherry = BundleItem.item_bundle("Cherry", 638, 1, 0)
lobster = BundleItem(fish_data.lobster, 1, 0)
crab = BundleItem(fish_data.crab, 1, 0)
shrimp = BundleItem(fish_data.shrimp, 1, 0)
crayfish = BundleItem(fish_data.crayfish, 1, 0)
snail = BundleItem(fish_data.snail, 1, 0)
periwinkle = BundleItem(fish_data.periwinkle, 1, 0)
trash = BundleItem.item_bundle("Trash", 168, 1, 0)
driftwood = BundleItem.item_bundle("Driftwood", 169, 1, 0)
soggy_newspaper = BundleItem.item_bundle("Soggy Newspaper", 172, 1, 0)
broken_cd = BundleItem.item_bundle("Broken CD", 171, 1, 0)
broken_glasses = BundleItem.item_bundle("Broken Glasses", 170, 1, 0)
chub = BundleItem(fish_data.chub, 1, 0)
catfish = BundleItem(fish_data.catfish, 1, 0)
rainbow_trout = BundleItem(fish_data.rainbow_trout, 1, 0)
lingcod = BundleItem(fish_data.lingcod, 1, 0)
walleye = BundleItem(fish_data.walleye, 1, 0)
perch = BundleItem(fish_data.perch, 1, 0)
pike = BundleItem(fish_data.pike, 1, 0)
bream = BundleItem(fish_data.bream, 1, 0)
salmon = BundleItem(fish_data.salmon, 1, 0)
sunfish = BundleItem(fish_data.sunfish, 1, 0)
tiger_trout = BundleItem(fish_data.tiger_trout, 1, 0)
shad = BundleItem(fish_data.shad, 1, 0)
smallmouth_bass = BundleItem(fish_data.smallmouth_bass, 1, 0)
dorado = BundleItem(fish_data.dorado, 1, 0)
carp = BundleItem(fish_data.carp, 1, 0)
midnight_carp = BundleItem(fish_data.midnight_carp, 1, 0)
largemouth_bass = BundleItem(fish_data.largemouth_bass, 1, 0)
sturgeon = BundleItem(fish_data.sturgeon, 1, 0)
bullhead = BundleItem(fish_data.bullhead, 1, 0)
tilapia = BundleItem(fish_data.tilapia, 1, 0)
pufferfish = BundleItem(fish_data.pufferfish, 1, 0)
tuna = BundleItem(fish_data.tuna, 1, 0)
super_cucumber = BundleItem(fish_data.super_cucumber, 1, 0)
flounder = BundleItem(fish_data.flounder, 1, 0)
anchovy = BundleItem(fish_data.anchovy, 1, 0)
sardine = BundleItem(fish_data.sardine, 1, 0)
red_mullet = BundleItem(fish_data.red_mullet, 1, 0)
herring = BundleItem(fish_data.herring, 1, 0)
eel = BundleItem(fish_data.eel, 1, 0)
octopus = BundleItem(fish_data.octopus, 1, 0)
red_snapper = BundleItem(fish_data.red_snapper, 1, 0)
squid = BundleItem(fish_data.squid, 1, 0)
sea_cucumber = BundleItem(fish_data.sea_cucumber, 1, 0)
albacore = BundleItem(fish_data.albacore, 1, 0)
halibut = BundleItem(fish_data.halibut, 1, 0)
scorpion_carp = BundleItem(fish_data.scorpion_carp, 1, 0)
sandfish = BundleItem(fish_data.sandfish, 1, 0)
woodskip = BundleItem(fish_data.woodskip, 1, 0)
lava_eel = BundleItem(fish_data.lava_eel, 1, 0)
ice_pip = BundleItem(fish_data.ice_pip, 1, 0)
stonefish = BundleItem(fish_data.stonefish, 1, 0)
ghostfish = BundleItem(fish_data.ghostfish, 1, 0)
wilted_bouquet = BundleItem.item_bundle("Wilted Bouquet", 277, 1, 0)
copper_bar = BundleItem.item_bundle("Copper Bar", 334, 2, 0)
iron_Bar = BundleItem.item_bundle("Iron Bar", 335, 2, 0)
gold_bar = BundleItem.item_bundle("Gold Bar", 336, 1, 0)
iridium_bar = BundleItem.item_bundle("Iridium Bar", 337, 1, 0)
refined_quartz = BundleItem.item_bundle("Refined Quartz", 338, 2, 0)
coal = BundleItem.item_bundle("Coal", 382, 5, 0)
quartz = BundleItem.item_bundle("Quartz", 80, 1, 0)
fire_quartz = BundleItem.item_bundle("Fire Quartz", 82, 1, 0)
frozen_tear = BundleItem.item_bundle("Frozen Tear", 84, 1, 0)
earth_crystal = BundleItem.item_bundle("Earth Crystal", 86, 1, 0)
emerald = BundleItem.item_bundle("Emerald", 60, 1, 0)
aquamarine = BundleItem.item_bundle("Aquamarine", 62, 1, 0)
ruby = BundleItem.item_bundle("Ruby", 64, 1, 0)
amethyst = BundleItem.item_bundle("Amethyst", 66, 1, 0)
topaz = BundleItem.item_bundle("Topaz", 68, 1, 0)
jade = BundleItem.item_bundle("Jade", 70, 1, 0)
slime = BundleItem.item_bundle("Slime", 766, 99, 0)
bug_meat = BundleItem.item_bundle("Bug Meat", 684, 10, 0)
bat_wing = BundleItem.item_bundle("Bat Wing", 767, 10, 0)
solar_essence = BundleItem.item_bundle("Solar Essence", 768, 1, 0)
void_essence = BundleItem.item_bundle("Void Essence", 769, 1, 0)
maki_roll = BundleItem.item_bundle("Maki Roll", 228, 1, 0)
fried_egg = BundleItem.item_bundle("Fried Egg", 194, 1, 0)
omelet = BundleItem.item_bundle("Omelet", 195, 1, 0)
pizza = BundleItem.item_bundle("Pizza", 206, 1, 0)
hashbrowns = BundleItem.item_bundle("Hashbrowns", 210, 1, 0)
pancakes = BundleItem.item_bundle("Pancakes", 211, 1, 0)
bread = BundleItem.item_bundle("Bread", 216, 1, 0)
tortilla = BundleItem.item_bundle("Tortilla", 229, 1, 0)
triple_shot_espresso = BundleItem.item_bundle("Triple Shot Espresso", 253, 1, 0)
farmer_s_lunch = BundleItem.item_bundle("Farmer's Lunch", 240, 1, 0)
survival_burger = BundleItem.item_bundle("Survival Burger", 241, 1, 0)
dish_o_the_sea = BundleItem.item_bundle("Dish O' The Sea", 242, 1, 0)
miner_s_treat = BundleItem.item_bundle("Miner's Treat", 243, 1, 0)
roots_platter = BundleItem.item_bundle("Roots Platter", 244, 1, 0)
salad = BundleItem.item_bundle("Salad", 196, 1, 0)
cheese_cauliflower = BundleItem.item_bundle("Cheese Cauliflower", 197, 1, 0)
parsnip_soup = BundleItem.item_bundle("Parsnip Soup", 199, 1, 0)
fried_mushroom = BundleItem.item_bundle("Fried Mushroom", 205, 1, 0)
salmon_dinner = BundleItem.item_bundle("Salmon Dinner", 212, 1, 0)
pepper_poppers = BundleItem.item_bundle("Pepper Poppers", 215, 1, 0)
spaghetti = BundleItem.item_bundle("Spaghetti", 224, 1, 0)
sashimi = BundleItem.item_bundle("Sashimi", 227, 1, 0)
blueberry_tart = BundleItem.item_bundle("Blueberry Tart", 234, 1, 0)
algae_soup = BundleItem.item_bundle("Algae Soup", 456, 1, 0)
pale_broth = BundleItem.item_bundle("Pale Broth", 457, 1, 0)
chowder = BundleItem.item_bundle("Chowder", 727, 1, 0)
green_algae = BundleItem.item_bundle("Green Algae", 153, 1, 0)
white_algae = BundleItem.item_bundle("White Algae", 157, 1, 0)
geode = BundleItem.item_bundle("Geode", 535, 1, 0)
frozen_geode = BundleItem.item_bundle("Frozen Geode", 536, 1, 0)
magma_geode = BundleItem.item_bundle("Magma Geode", 537, 1, 0)
omni_geode = BundleItem.item_bundle("Omni Geode", 749, 1, 0)
spring_foraging_items = [wild_horseradish, daffodil, leek, dandelion, salmonberry, spring_onion]
summer_foraging_items = [grape, spice_berry, sweet_pea, fiddlehead_fern, rainbow_shell]
fall_foraging_items = [common_mushroom, wild_plum, hazelnut, blackberry]
winter_foraging_items = [winter_root, crystal_fruit, snow_yam, crocus, holly, nautilus_shell]
exotic_foraging_items = [coconut, cactus_fruit, cave_carrot, red_mushroom, purple_mushroom,
maple_syrup, oak_resin, pine_tar, morel, coral,
sea_urchin, clam, cockle, mussel, oyster, seaweed]
construction_items = [wood, stone, hardwood, clay, fiber]
# TODO coffee_bean, garlic, rhubarb, tea_leaves
spring_crop_items = [blue_jazz, cauliflower, green_bean, kale, parsnip, potato, strawberry, tulip, unmilled_rice]
# TODO red_cabbage, starfruit, ancient_fruit, pineapple, taro_root
summer_crops_items = [blueberry, corn, hops, hot_pepper, melon, poppy,
radish, summer_spangle, sunflower, tomato, wheat]
# TODO artichoke, beet
fall_crops_items = [corn, sunflower, wheat, amaranth, bok_choy, cranberries,
eggplant, fairy_rose, grape, pumpkin, yam, sweet_gem_berry]
all_crops_items = sorted({*spring_crop_items, *summer_crops_items, *fall_crops_items})
quality_crops_items = [item.as_quality(2).as_amount(5) for item in all_crops_items]
# TODO void_egg, dinosaur_egg, ostrich_egg, golden_egg
animal_product_items = [egg, large_egg, brown_egg, large_brown_egg, wool, milk, large_milk,
goat_milk, large_goat_milk, truffle, duck_feather, duck_egg, rabbit_foot]
# TODO coffee, green_tea
artisan_goods_items = [truffle_oil, cloth, goat_cheese, cheese, honey, beer, juice, mead, pale_ale, wine, jelly,
pickles, caviar, aged_roe, apple, apricot, orange, peach, pomegranate, cherry]
river_fish_items = [chub, catfish, rainbow_trout, lingcod, walleye, perch, pike, bream,
salmon, sunfish, tiger_trout, shad, smallmouth_bass, dorado]
lake_fish_items = [chub, rainbow_trout, lingcod, walleye, perch, carp, midnight_carp,
largemouth_bass, sturgeon, bullhead, midnight_carp]
ocean_fish_items = [tilapia, pufferfish, tuna, super_cucumber, flounder, anchovy, sardine, red_mullet,
herring, eel, octopus, red_snapper, squid, sea_cucumber, albacore, halibut]
night_fish_items = [walleye, bream, super_cucumber, eel, squid, midnight_carp]
# TODO void_salmon
specialty_fish_items = [scorpion_carp, sandfish, woodskip, pufferfish, eel, octopus,
squid, lava_eel, ice_pip, stonefish, ghostfish, dorado]
crab_pot_items = [lobster, clam, crab, cockle, mussel, shrimp, oyster, crayfish, snail,
periwinkle, trash, driftwood, soggy_newspaper, broken_cd, broken_glasses]
# TODO radioactive_bar
blacksmith_items = [wilted_bouquet, copper_bar, iron_Bar, gold_bar, iridium_bar, refined_quartz, coal]
geologist_items = [quartz, earth_crystal, frozen_tear, fire_quartz, emerald, aquamarine, ruby, amethyst, topaz, jade]
adventurer_items = [slime, bug_meat, bat_wing, solar_essence, void_essence, coal]
chef_items = [maki_roll, fried_egg, omelet, pizza, hashbrowns, pancakes, bread, tortilla, triple_shot_espresso,
farmer_s_lunch, survival_burger, dish_o_the_sea, miner_s_treat, roots_platter, salad,
cheese_cauliflower, parsnip_soup, fried_mushroom, salmon_dinner, pepper_poppers, spaghetti,
sashimi, blueberry_tart, algae_soup, pale_broth, chowder]
dwarf_scroll_1 = BundleItem.item_bundle("Dwarf Scroll I", 96, 1, 0)
dwarf_scroll_2 = BundleItem.item_bundle("Dwarf Scroll II", 97, 1, 0)
dwarf_scroll_3 = BundleItem.item_bundle("Dwarf Scroll III", 98, 1, 0)
dwarf_scroll_4 = BundleItem.item_bundle("Dwarf Scroll IV", 99, 1, 0)
elvish_jewelry = BundleItem.item_bundle("Elvish Jewelry", 104, 1, 0)
ancient_drum = BundleItem.item_bundle("Ancient Drum", 123, 1, 0)
dried_starfish = BundleItem.item_bundle("Dried Starfish", 116, 1, 0)
# TODO Dye Bundle
dye_red_items = [cranberries, dwarf_scroll_1, hot_pepper, radish, rhubarb, spaghetti, strawberry, tomato, tulip]
dye_orange_items = [poppy, pumpkin, apricot, orange, spice_berry, winter_root]
dye_yellow_items = [dried_starfish, dwarf_scroll_4, elvish_jewelry, corn, parsnip, summer_spangle, sunflower]
dye_green_items = [dwarf_scroll_2, fiddlehead_fern, kale, artichoke, bok_choy, green_bean]
dye_blue_items = [blueberry, dwarf_scroll_3, blue_jazz, blackberry, crystal_fruit]
dye_purple_items = [ancient_drum, beet, crocus, eggplant, red_cabbage, sweet_pea]
dye_items = [dye_red_items, dye_orange_items, dye_yellow_items, dye_green_items, dye_blue_items, dye_purple_items]
field_research_items = [purple_mushroom, nautilus_shell, chub, geode, frozen_geode, magma_geode, omni_geode,
rainbow_shell, amethyst, bream, carp]
fodder_items = [wheat.as_amount(10), hay.as_amount(10), apple.as_amount(3), kale.as_amount(3), corn.as_amount(3),
green_bean.as_amount(3), potato.as_amount(3), green_algae.as_amount(5), white_algae.as_amount(3)]
enchanter_items = [oak_resin, wine, rabbit_foot, pomegranate, purple_mushroom, solar_essence,
super_cucumber, void_essence, fire_quartz, frozen_tear, jade]
vault_2500_items = [BundleItem.money_bundle(2500)]
vault_5000_items = [BundleItem.money_bundle(5000)]
vault_10000_items = [BundleItem.money_bundle(10000)]
vault_25000_items = [BundleItem.money_bundle(25000)]
crafts_room_bundle_items = [
*spring_foraging_items,
*summer_foraging_items,
*fall_foraging_items,
*winter_foraging_items,
*exotic_foraging_items,
*construction_items,
]
pantry_bundle_items = sorted({
*spring_crop_items,
*summer_crops_items,
*fall_crops_items,
*quality_crops_items,
*animal_product_items,
*artisan_goods_items,
})
fish_tank_bundle_items = sorted({
*river_fish_items,
*lake_fish_items,
*ocean_fish_items,
*night_fish_items,
*crab_pot_items,
*specialty_fish_items,
})
boiler_room_bundle_items = sorted({
*blacksmith_items,
*geologist_items,
*adventurer_items,
})
bulletin_board_bundle_items = sorted({
*chef_items,
*[item for dye_color_items in dye_items for item in dye_color_items],
*field_research_items,
*fodder_items,
*enchanter_items
})
vault_bundle_items = [
*vault_2500_items,
*vault_5000_items,
*vault_10000_items,
*vault_25000_items,
]
all_bundle_items_except_money = sorted({
*crafts_room_bundle_items,
*pantry_bundle_items,
*fish_tank_bundle_items,
*boiler_room_bundle_items,
*bulletin_board_bundle_items,
}, key=lambda x: x.item.name)
all_bundle_items = sorted({
*crafts_room_bundle_items,
*pantry_bundle_items,
*fish_tank_bundle_items,
*boiler_room_bundle_items,
*bulletin_board_bundle_items,
*vault_bundle_items,
}, key=lambda x: x.item.name)
all_bundle_items_by_name = {item.item.name: item for item in all_bundle_items}
all_bundle_items_by_id = {item.item.item_id: item for item in all_bundle_items}

View File

@@ -0,0 +1,254 @@
from random import Random
from typing import List, Dict, Union
from .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: int, price: int) -> 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)

View File

View File

@@ -0,0 +1,312 @@
id,name,classification,groups
0,Joja Cola,filler,TRASH
15,Rusty Key,progression,
16,Dwarvish Translation Guide,progression,
17,Bridge Repair,progression,COMMUNITY_REWARD
18,Greenhouse,progression,COMMUNITY_REWARD
19,Glittering Boulder Removed,progression,COMMUNITY_REWARD
20,Minecarts Repair,useful,COMMUNITY_REWARD
21,Bus Repair,progression,COMMUNITY_REWARD
22,Movie Theater,useful,
23,Stardrop,useful,
24,Progressive Backpack,progression,
25,Rusty Sword,progression,WEAPON
26,Leather Boots,progression,"FOOTWEAR,MINES_FLOOR_10"
27,Work Boots,useful,"FOOTWEAR,MINES_FLOOR_10"
28,Wooden Blade,progression,"MINES_FLOOR_10,WEAPON"
29,Iron Dirk,progression,"MINES_FLOOR_10,WEAPON"
30,Wind Spire,progression,"MINES_FLOOR_10,WEAPON"
31,Femur,progression,"MINES_FLOOR_10,WEAPON"
32,Steel Smallsword,progression,"MINES_FLOOR_20,WEAPON"
33,Wood Club,progression,"MINES_FLOOR_20,WEAPON"
34,Elf Blade,progression,"MINES_FLOOR_20,WEAPON"
35,Glow Ring,useful,"MINES_FLOOR_20,RING"
36,Magnet Ring,useful,"MINES_FLOOR_20,RING"
37,Slingshot,progression,WEAPON
38,Tundra Boots,useful,"FOOTWEAR,MINES_FLOOR_50"
39,Thermal Boots,useful,"FOOTWEAR,MINES_FLOOR_50"
40,Combat Boots,useful,"FOOTWEAR,MINES_FLOOR_50"
41,Silver Saber,progression,"MINES_FLOOR_50,WEAPON"
42,Pirate's Sword,progression,"MINES_FLOOR_50,WEAPON"
43,Crystal Dagger,progression,"MINES_FLOOR_60,WEAPON"
44,Cutlass,progression,"MINES_FLOOR_60,WEAPON"
45,Iron Edge,progression,"MINES_FLOOR_60,WEAPON"
46,Burglar's Shank,progression,"MINES_FLOOR_60,WEAPON"
47,Wood Mallet,progression,"MINES_FLOOR_60,WEAPON"
48,Master Slingshot,progression,WEAPON
49,Firewalker Boots,useful,"FOOTWEAR,MINES_FLOOR_80"
50,Dark Boots,useful,"FOOTWEAR,MINES_FLOOR_80"
51,Claymore,progression,"MINES_FLOOR_80,WEAPON"
52,Templar's Blade,progression,"MINES_FLOOR_80,WEAPON"
53,Kudgel,progression,"MINES_FLOOR_80,WEAPON"
54,Shadow Dagger,progression,"MINES_FLOOR_80,WEAPON"
55,Obsidian Edge,progression,"MINES_FLOOR_90,WEAPON"
56,Tempered Broadsword,progression,"MINES_FLOOR_90,WEAPON"
57,Wicked Kris,progression,"MINES_FLOOR_90,WEAPON"
58,Bone Sword,progression,"MINES_FLOOR_90,WEAPON"
59,Ossified Blade,progression,"MINES_FLOOR_90,WEAPON"
60,Space Boots,useful,"FOOTWEAR,MINES_FLOOR_110"
61,Crystal Shoes,useful,"FOOTWEAR,MINES_FLOOR_110"
62,Steel Falchion,progression,"MINES_FLOOR_110,WEAPON"
63,The Slammer,progression,"MINES_FLOOR_110,WEAPON"
64,Skull Key,progression,
65,Progressive Hoe,progression,PROGRESSIVE_TOOLS
66,Progressive Pickaxe,progression,PROGRESSIVE_TOOLS
67,Progressive Axe,progression,PROGRESSIVE_TOOLS
68,Progressive Watering Can,progression,PROGRESSIVE_TOOLS
69,Progressive Trash Can,progression,PROGRESSIVE_TOOLS
70,Progressive Fishing Rod,progression,PROGRESSIVE_TOOLS
71,Golden Scythe,useful,
72,Progressive Mine Elevator,progression,
73,Farming Level,progression,SKILL_LEVEL_UP
74,Fishing Level,progression,SKILL_LEVEL_UP
75,Foraging Level,progression,SKILL_LEVEL_UP
76,Mining Level,progression,SKILL_LEVEL_UP
77,Combat Level,progression,SKILL_LEVEL_UP
78,Earth Obelisk,useful,
79,Water Obelisk,useful,
80,Desert Obelisk,progression,
81,Island Obelisk,progression,
82,Junimo Hut,useful,
83,Gold Clock,useful,
84,Progressive Coop,progression,
85,Progressive Barn,progression,
86,Well,useful,
87,Silo,progression,
88,Mill,progression,
89,Progressive Shed,progression,
90,Fish Pond,progression,
91,Stable,useful,
92,Slime Hutch,useful,
93,Shipping Bin,progression,
94,Beach Bridge,progression,
95,Adventurer's Guild,progression,
96,Club Card,progression,
97,Magnifying Glass,progression,
98,Bear's Knowledge,progression,
99,Iridium Snake Milk,progression,
100,JotPK: Progressive Boots,progression,ARCADE_MACHINE_BUFFS
101,JotPK: Progressive Gun,progression,ARCADE_MACHINE_BUFFS
102,JotPK: Progressive Ammo,progression,ARCADE_MACHINE_BUFFS
103,JotPK: Extra Life,progression,ARCADE_MACHINE_BUFFS
104,JotPK: Increased Drop Rate,progression,ARCADE_MACHINE_BUFFS
105,Junimo Kart: Extra Life,progression,ARCADE_MACHINE_BUFFS
106,Galaxy Sword,progression,"GALAXY_WEAPONS,WEAPON"
107,Galaxy Dagger,progression,"GALAXY_WEAPONS,WEAPON"
108,Galaxy Hammer,progression,"GALAXY_WEAPONS,WEAPON"
109,Movement Speed Bonus,useful,
110,Luck Bonus,useful,
111,Lava Katana,progression,"MINES_FLOOR_110,WEAPON"
112,Progressive House,progression,
113,Traveling Merchant: Sunday,progression,
114,Traveling Merchant: Monday,progression,
115,Traveling Merchant: Tuesday,progression,
116,Traveling Merchant: Wednesday,progression,
117,Traveling Merchant: Thursday,progression,
118,Traveling Merchant: Friday,progression,
119,Traveling Merchant: Saturday,progression,
120,Traveling Merchant Stock Size,progression,
121,Traveling Merchant Discount,progression,
122,Return Scepter,useful,
5000,Resource Pack: 500 Money,useful,"BASE_RESOURCE,RESOURCE_PACK"
5001,Resource Pack: 1000 Money,useful,"BASE_RESOURCE,RESOURCE_PACK"
5002,Resource Pack: 1500 Money,useful,"BASE_RESOURCE,RESOURCE_PACK"
5003,Resource Pack: 2000 Money,useful,"BASE_RESOURCE,RESOURCE_PACK"
5004,Resource Pack: 25 Stone,filler,"BASE_RESOURCE,RESOURCE_PACK"
5005,Resource Pack: 50 Stone,filler,"BASE_RESOURCE,RESOURCE_PACK"
5006,Resource Pack: 75 Stone,filler,"BASE_RESOURCE,RESOURCE_PACK"
5007,Resource Pack: 100 Stone,filler,"BASE_RESOURCE,RESOURCE_PACK"
5008,Resource Pack: 25 Wood,filler,"BASE_RESOURCE,RESOURCE_PACK"
5009,Resource Pack: 50 Wood,filler,"BASE_RESOURCE,RESOURCE_PACK"
5010,Resource Pack: 75 Wood,filler,"BASE_RESOURCE,RESOURCE_PACK"
5011,Resource Pack: 100 Wood,filler,"BASE_RESOURCE,RESOURCE_PACK"
5012,Resource Pack: 5 Hardwood,useful,"BASE_RESOURCE,RESOURCE_PACK"
5013,Resource Pack: 10 Hardwood,useful,"BASE_RESOURCE,RESOURCE_PACK"
5014,Resource Pack: 15 Hardwood,useful,"BASE_RESOURCE,RESOURCE_PACK"
5015,Resource Pack: 20 Hardwood,useful,"BASE_RESOURCE,RESOURCE_PACK"
5016,Resource Pack: 15 Fiber,filler,"BASE_RESOURCE,RESOURCE_PACK"
5017,Resource Pack: 30 Fiber,filler,"BASE_RESOURCE,RESOURCE_PACK"
5018,Resource Pack: 45 Fiber,filler,"BASE_RESOURCE,RESOURCE_PACK"
5019,Resource Pack: 60 Fiber,filler,"BASE_RESOURCE,RESOURCE_PACK"
5020,Resource Pack: 5 Coal,filler,"BASE_RESOURCE,RESOURCE_PACK"
5021,Resource Pack: 10 Coal,filler,"BASE_RESOURCE,RESOURCE_PACK"
5022,Resource Pack: 15 Coal,filler,"BASE_RESOURCE,RESOURCE_PACK"
5023,Resource Pack: 20 Coal,filler,"BASE_RESOURCE,RESOURCE_PACK"
5024,Resource Pack: 5 Clay,filler,"BASE_RESOURCE,RESOURCE_PACK"
5025,Resource Pack: 10 Clay,filler,"BASE_RESOURCE,RESOURCE_PACK"
5026,Resource Pack: 15 Clay,filler,"BASE_RESOURCE,RESOURCE_PACK"
5027,Resource Pack: 20 Clay,filler,"BASE_RESOURCE,RESOURCE_PACK"
5028,Resource Pack: 1 Warp Totem: Beach,filler,"RESOURCE_PACK,WARP_TOTEM"
5029,Resource Pack: 3 Warp Totem: Beach,filler,"RESOURCE_PACK,WARP_TOTEM"
5030,Resource Pack: 5 Warp Totem: Beach,filler,"RESOURCE_PACK,WARP_TOTEM"
5031,Resource Pack: 7 Warp Totem: Beach,filler,"RESOURCE_PACK,WARP_TOTEM"
5032,Resource Pack: 9 Warp Totem: Beach,filler,"RESOURCE_PACK,WARP_TOTEM"
5033,Resource Pack: 10 Warp Totem: Beach,filler,"RESOURCE_PACK,WARP_TOTEM"
5034,Resource Pack: 1 Warp Totem: Desert,filler,"RESOURCE_PACK,WARP_TOTEM"
5035,Resource Pack: 3 Warp Totem: Desert,filler,"RESOURCE_PACK,WARP_TOTEM"
5036,Resource Pack: 5 Warp Totem: Desert,filler,"RESOURCE_PACK,WARP_TOTEM"
5037,Resource Pack: 7 Warp Totem: Desert,filler,"RESOURCE_PACK,WARP_TOTEM"
5038,Resource Pack: 9 Warp Totem: Desert,filler,"RESOURCE_PACK,WARP_TOTEM"
5039,Resource Pack: 10 Warp Totem: Desert,filler,"RESOURCE_PACK,WARP_TOTEM"
5040,Resource Pack: 1 Warp Totem: Farm,filler,"RESOURCE_PACK,WARP_TOTEM"
5041,Resource Pack: 3 Warp Totem: Farm,filler,"RESOURCE_PACK,WARP_TOTEM"
5042,Resource Pack: 5 Warp Totem: Farm,filler,"RESOURCE_PACK,WARP_TOTEM"
5043,Resource Pack: 7 Warp Totem: Farm,filler,"RESOURCE_PACK,WARP_TOTEM"
5044,Resource Pack: 9 Warp Totem: Farm,filler,"RESOURCE_PACK,WARP_TOTEM"
5045,Resource Pack: 10 Warp Totem: Farm,filler,"RESOURCE_PACK,WARP_TOTEM"
5046,Resource Pack: 1 Warp Totem: Island,filler,"RESOURCE_PACK,WARP_TOTEM"
5047,Resource Pack: 3 Warp Totem: Island,filler,"RESOURCE_PACK,WARP_TOTEM"
5048,Resource Pack: 5 Warp Totem: Island,filler,"RESOURCE_PACK,WARP_TOTEM"
5049,Resource Pack: 7 Warp Totem: Island,filler,"RESOURCE_PACK,WARP_TOTEM"
5050,Resource Pack: 9 Warp Totem: Island,filler,"RESOURCE_PACK,WARP_TOTEM"
5051,Resource Pack: 10 Warp Totem: Island,filler,"RESOURCE_PACK,WARP_TOTEM"
5052,Resource Pack: 1 Warp Totem: Mountains,filler,"RESOURCE_PACK,WARP_TOTEM"
5053,Resource Pack: 3 Warp Totem: Mountains,filler,"RESOURCE_PACK,WARP_TOTEM"
5054,Resource Pack: 5 Warp Totem: Mountains,filler,"RESOURCE_PACK,WARP_TOTEM"
5055,Resource Pack: 7 Warp Totem: Mountains,filler,"RESOURCE_PACK,WARP_TOTEM"
5056,Resource Pack: 9 Warp Totem: Mountains,filler,"RESOURCE_PACK,WARP_TOTEM"
5057,Resource Pack: 10 Warp Totem: Mountains,filler,"RESOURCE_PACK,WARP_TOTEM"
5058,Resource Pack: 6 Geode,filler,"GEODE,RESOURCE_PACK"
5059,Resource Pack: 12 Geode,filler,"GEODE,RESOURCE_PACK"
5060,Resource Pack: 18 Geode,filler,"GEODE,RESOURCE_PACK"
5061,Resource Pack: 24 Geode,filler,"GEODE,RESOURCE_PACK"
5062,Resource Pack: 4 Frozen Geode,filler,"GEODE,RESOURCE_PACK"
5063,Resource Pack: 8 Frozen Geode,filler,"GEODE,RESOURCE_PACK"
5064,Resource Pack: 12 Frozen Geode,filler,"GEODE,RESOURCE_PACK"
5065,Resource Pack: 16 Frozen Geode,filler,"GEODE,RESOURCE_PACK"
5066,Resource Pack: 3 Magma Geode,filler,"GEODE,RESOURCE_PACK"
5067,Resource Pack: 6 Magma Geode,filler,"GEODE,RESOURCE_PACK"
5068,Resource Pack: 9 Magma Geode,filler,"GEODE,RESOURCE_PACK"
5069,Resource Pack: 12 Magma Geode,filler,"GEODE,RESOURCE_PACK"
5070,Resource Pack: 2 Omni Geode,useful,"GEODE,RESOURCE_PACK"
5071,Resource Pack: 4 Omni Geode,useful,"GEODE,RESOURCE_PACK"
5072,Resource Pack: 6 Omni Geode,useful,"GEODE,RESOURCE_PACK"
5073,Resource Pack: 8 Omni Geode,useful,"GEODE,RESOURCE_PACK"
5074,Resource Pack: 25 Copper Ore,filler,"ORE,RESOURCE_PACK"
5075,Resource Pack: 50 Copper Ore,filler,"ORE,RESOURCE_PACK"
5076,Resource Pack: 75 Copper Ore,filler,"ORE,RESOURCE_PACK"
5077,Resource Pack: 100 Copper Ore,filler,"ORE,RESOURCE_PACK"
5078,Resource Pack: 125 Copper Ore,filler,"ORE,RESOURCE_PACK"
5079,Resource Pack: 150 Copper Ore,filler,"ORE,RESOURCE_PACK"
5080,Resource Pack: 25 Iron Ore,filler,"ORE,RESOURCE_PACK"
5081,Resource Pack: 50 Iron Ore,filler,"ORE,RESOURCE_PACK"
5082,Resource Pack: 75 Iron Ore,filler,"ORE,RESOURCE_PACK"
5083,Resource Pack: 100 Iron Ore,filler,"ORE,RESOURCE_PACK"
5084,Resource Pack: 12 Gold Ore,useful,"ORE,RESOURCE_PACK"
5085,Resource Pack: 25 Gold Ore,useful,"ORE,RESOURCE_PACK"
5086,Resource Pack: 38 Gold Ore,useful,"ORE,RESOURCE_PACK"
5087,Resource Pack: 50 Gold Ore,useful,"ORE,RESOURCE_PACK"
5088,Resource Pack: 5 Iridium Ore,useful,"ORE,RESOURCE_PACK"
5089,Resource Pack: 10 Iridium Ore,useful,"ORE,RESOURCE_PACK"
5090,Resource Pack: 15 Iridium Ore,useful,"ORE,RESOURCE_PACK"
5091,Resource Pack: 20 Iridium Ore,useful,"ORE,RESOURCE_PACK"
5092,Resource Pack: 5 Quartz,filler,"ORE,RESOURCE_PACK"
5093,Resource Pack: 10 Quartz,filler,"ORE,RESOURCE_PACK"
5094,Resource Pack: 15 Quartz,filler,"ORE,RESOURCE_PACK"
5095,Resource Pack: 20 Quartz,filler,"ORE,RESOURCE_PACK"
5096,Resource Pack: 10 Basic Fertilizer,filler,"FERTILIZER,RESOURCE_PACK"
5097,Resource Pack: 20 Basic Fertilizer,filler,"FERTILIZER,RESOURCE_PACK"
5098,Resource Pack: 30 Basic Fertilizer,filler,"FERTILIZER,RESOURCE_PACK"
5099,Resource Pack: 40 Basic Fertilizer,filler,"FERTILIZER,RESOURCE_PACK"
5100,Resource Pack: 50 Basic Fertilizer,filler,"FERTILIZER,RESOURCE_PACK"
5101,Resource Pack: 60 Basic Fertilizer,filler,"FERTILIZER,RESOURCE_PACK"
5102,Resource Pack: 10 Basic Retaining Soil,filler,"FERTILIZER,RESOURCE_PACK"
5103,Resource Pack: 20 Basic Retaining Soil,filler,"FERTILIZER,RESOURCE_PACK"
5104,Resource Pack: 30 Basic Retaining Soil,filler,"FERTILIZER,RESOURCE_PACK"
5105,Resource Pack: 40 Basic Retaining Soil,filler,"FERTILIZER,RESOURCE_PACK"
5106,Resource Pack: 50 Basic Retaining Soil,filler,"FERTILIZER,RESOURCE_PACK"
5107,Resource Pack: 60 Basic Retaining Soil,filler,"FERTILIZER,RESOURCE_PACK"
5108,Resource Pack: 10 Speed-Gro,filler,"FERTILIZER,RESOURCE_PACK"
5109,Resource Pack: 20 Speed-Gro,filler,"FERTILIZER,RESOURCE_PACK"
5110,Resource Pack: 30 Speed-Gro,filler,"FERTILIZER,RESOURCE_PACK"
5111,Resource Pack: 40 Speed-Gro,filler,"FERTILIZER,RESOURCE_PACK"
5112,Resource Pack: 50 Speed-Gro,filler,"FERTILIZER,RESOURCE_PACK"
5113,Resource Pack: 60 Speed-Gro,filler,"FERTILIZER,RESOURCE_PACK"
5114,Resource Pack: 4 Quality Fertilizer,filler,"FERTILIZER,RESOURCE_PACK"
5115,Resource Pack: 12 Quality Fertilizer,filler,"FERTILIZER,RESOURCE_PACK"
5116,Resource Pack: 20 Quality Fertilizer,filler,"FERTILIZER,RESOURCE_PACK"
5117,Resource Pack: 28 Quality Fertilizer,filler,"FERTILIZER,RESOURCE_PACK"
5118,Resource Pack: 36 Quality Fertilizer,filler,"FERTILIZER,RESOURCE_PACK"
5119,Resource Pack: 40 Quality Fertilizer,filler,"FERTILIZER,RESOURCE_PACK"
5120,Resource Pack: 4 Quality Retaining Soil,filler,"FERTILIZER,RESOURCE_PACK"
5121,Resource Pack: 12 Quality Retaining Soil,filler,"FERTILIZER,RESOURCE_PACK"
5122,Resource Pack: 20 Quality Retaining Soil,filler,"FERTILIZER,RESOURCE_PACK"
5123,Resource Pack: 28 Quality Retaining Soil,filler,"FERTILIZER,RESOURCE_PACK"
5124,Resource Pack: 36 Quality Retaining Soil,filler,"FERTILIZER,RESOURCE_PACK"
5125,Resource Pack: 40 Quality Retaining Soil,filler,"FERTILIZER,RESOURCE_PACK"
5126,Resource Pack: 4 Deluxe Speed-Gro,filler,"FERTILIZER,RESOURCE_PACK"
5127,Resource Pack: 12 Deluxe Speed-Gro,filler,"FERTILIZER,RESOURCE_PACK"
5128,Resource Pack: 20 Deluxe Speed-Gro,filler,"FERTILIZER,RESOURCE_PACK"
5129,Resource Pack: 28 Deluxe Speed-Gro,filler,"FERTILIZER,RESOURCE_PACK"
5130,Resource Pack: 36 Deluxe Speed-Gro,filler,"FERTILIZER,RESOURCE_PACK"
5131,Resource Pack: 40 Deluxe Speed-Gro,filler,"FERTILIZER,RESOURCE_PACK"
5132,Resource Pack: 2 Deluxe Fertilizer,useful,"FERTILIZER,RESOURCE_PACK"
5133,Resource Pack: 6 Deluxe Fertilizer,useful,"FERTILIZER,RESOURCE_PACK"
5134,Resource Pack: 10 Deluxe Fertilizer,useful,"FERTILIZER,RESOURCE_PACK"
5135,Resource Pack: 14 Deluxe Fertilizer,useful,"FERTILIZER,RESOURCE_PACK"
5136,Resource Pack: 18 Deluxe Fertilizer,useful,"FERTILIZER,RESOURCE_PACK"
5137,Resource Pack: 20 Deluxe Fertilizer,useful,"FERTILIZER,RESOURCE_PACK"
5138,Resource Pack: 2 Deluxe Retaining Soil,useful,"FERTILIZER,RESOURCE_PACK"
5139,Resource Pack: 6 Deluxe Retaining Soil,useful,"FERTILIZER,RESOURCE_PACK"
5140,Resource Pack: 10 Deluxe Retaining Soil,useful,"FERTILIZER,RESOURCE_PACK"
5141,Resource Pack: 14 Deluxe Retaining Soil,useful,"FERTILIZER,RESOURCE_PACK"
5142,Resource Pack: 18 Deluxe Retaining Soil,useful,"FERTILIZER,RESOURCE_PACK"
5143,Resource Pack: 20 Deluxe Retaining Soil,useful,"FERTILIZER,RESOURCE_PACK"
5144,Resource Pack: 2 Hyper Speed-Gro,useful,"FERTILIZER,RESOURCE_PACK"
5145,Resource Pack: 6 Hyper Speed-Gro,useful,"FERTILIZER,RESOURCE_PACK"
5146,Resource Pack: 10 Hyper Speed-Gro,useful,"FERTILIZER,RESOURCE_PACK"
5147,Resource Pack: 14 Hyper Speed-Gro,useful,"FERTILIZER,RESOURCE_PACK"
5148,Resource Pack: 18 Hyper Speed-Gro,useful,"FERTILIZER,RESOURCE_PACK"
5149,Resource Pack: 20 Hyper Speed-Gro,useful,"FERTILIZER,RESOURCE_PACK"
5150,Resource Pack: 2 Tree Fertilizer,filler,"FERTILIZER,RESOURCE_PACK"
5151,Resource Pack: 6 Tree Fertilizer,filler,"FERTILIZER,RESOURCE_PACK"
5152,Resource Pack: 10 Tree Fertilizer,filler,"FERTILIZER,RESOURCE_PACK"
5153,Resource Pack: 14 Tree Fertilizer,filler,"FERTILIZER,RESOURCE_PACK"
5154,Resource Pack: 18 Tree Fertilizer,filler,"FERTILIZER,RESOURCE_PACK"
5155,Resource Pack: 20 Tree Fertilizer,filler,"FERTILIZER,RESOURCE_PACK"
5156,Resource Pack: 10 Spring Seeds,filler,"RESOURCE_PACK,SEED"
5157,Resource Pack: 20 Spring Seeds,filler,"RESOURCE_PACK,SEED"
5158,Resource Pack: 30 Spring Seeds,filler,"RESOURCE_PACK,SEED"
5159,Resource Pack: 40 Spring Seeds,filler,"RESOURCE_PACK,SEED"
5160,Resource Pack: 50 Spring Seeds,filler,"RESOURCE_PACK,SEED"
5161,Resource Pack: 60 Spring Seeds,filler,"RESOURCE_PACK,SEED"
5162,Resource Pack: 10 Summer Seeds,filler,"RESOURCE_PACK,SEED"
5163,Resource Pack: 20 Summer Seeds,filler,"RESOURCE_PACK,SEED"
5164,Resource Pack: 30 Summer Seeds,filler,"RESOURCE_PACK,SEED"
5165,Resource Pack: 40 Summer Seeds,filler,"RESOURCE_PACK,SEED"
5166,Resource Pack: 50 Summer Seeds,filler,"RESOURCE_PACK,SEED"
5167,Resource Pack: 60 Summer Seeds,filler,"RESOURCE_PACK,SEED"
5168,Resource Pack: 10 Fall Seeds,filler,"RESOURCE_PACK,SEED"
5169,Resource Pack: 20 Fall Seeds,filler,"RESOURCE_PACK,SEED"
5170,Resource Pack: 30 Fall Seeds,filler,"RESOURCE_PACK,SEED"
5171,Resource Pack: 40 Fall Seeds,filler,"RESOURCE_PACK,SEED"
5172,Resource Pack: 50 Fall Seeds,filler,"RESOURCE_PACK,SEED"
5173,Resource Pack: 60 Fall Seeds,filler,"RESOURCE_PACK,SEED"
5174,Resource Pack: 10 Winter Seeds,filler,"RESOURCE_PACK,SEED"
5175,Resource Pack: 20 Winter Seeds,filler,"RESOURCE_PACK,SEED"
5176,Resource Pack: 30 Winter Seeds,filler,"RESOURCE_PACK,SEED"
5177,Resource Pack: 40 Winter Seeds,filler,"RESOURCE_PACK,SEED"
5178,Resource Pack: 50 Winter Seeds,filler,"RESOURCE_PACK,SEED"
5179,Resource Pack: 60 Winter Seeds,filler,"RESOURCE_PACK,SEED"
5180,Resource Pack: 1 Mahogany Seed,filler,"RESOURCE_PACK,SEED"
5181,Resource Pack: 3 Mahogany Seed,filler,"RESOURCE_PACK,SEED"
5182,Resource Pack: 5 Mahogany Seed,filler,"RESOURCE_PACK,SEED"
5183,Resource Pack: 7 Mahogany Seed,filler,"RESOURCE_PACK,SEED"
5184,Resource Pack: 9 Mahogany Seed,filler,"RESOURCE_PACK,SEED"
5185,Resource Pack: 10 Mahogany Seed,filler,"RESOURCE_PACK,SEED"
5186,Resource Pack: 10 Bait,filler,"FISHING_RESOURCE,RESOURCE_PACK"
5187,Resource Pack: 20 Bait,filler,"FISHING_RESOURCE,RESOURCE_PACK"
5188,Resource Pack: 30 Bait,filler,"FISHING_RESOURCE,RESOURCE_PACK"
5189,Resource Pack: 40 Bait,filler,"FISHING_RESOURCE,RESOURCE_PACK"
5190,Resource Pack: 50 Bait,filler,"FISHING_RESOURCE,RESOURCE_PACK"
5191,Resource Pack: 60 Bait,filler,"FISHING_RESOURCE,RESOURCE_PACK"
5192,Resource Pack: 1 Crab Pot,filler,"FISHING_RESOURCE,RESOURCE_PACK"
5193,Resource Pack: 2 Crab Pot,filler,"FISHING_RESOURCE,RESOURCE_PACK"
5194,Resource Pack: 3 Crab Pot,filler,"FISHING_RESOURCE,RESOURCE_PACK"
5195,Resource Pack: 4 Crab Pot,filler,"FISHING_RESOURCE,RESOURCE_PACK"
5196,Resource Pack: 5 Crab Pot,filler,"FISHING_RESOURCE,RESOURCE_PACK"
5197,Resource Pack: 6 Crab Pot,filler,"FISHING_RESOURCE,RESOURCE_PACK"
5198,Friendship Bonus (1 <3),useful,FRIENDSHIP_PACK
5199,Friendship Bonus (2 <3),useful,FRIENDSHIP_PACK
5200,Friendship Bonus (3 <3),useful,FRIENDSHIP_PACK
5201,Friendship Bonus (4 <3),useful,FRIENDSHIP_PACK
1 id name classification groups
2 0 Joja Cola filler TRASH
3 15 Rusty Key progression
4 16 Dwarvish Translation Guide progression
5 17 Bridge Repair progression COMMUNITY_REWARD
6 18 Greenhouse progression COMMUNITY_REWARD
7 19 Glittering Boulder Removed progression COMMUNITY_REWARD
8 20 Minecarts Repair useful COMMUNITY_REWARD
9 21 Bus Repair progression COMMUNITY_REWARD
10 22 Movie Theater useful
11 23 Stardrop useful
12 24 Progressive Backpack progression
13 25 Rusty Sword progression WEAPON
14 26 Leather Boots progression FOOTWEAR,MINES_FLOOR_10
15 27 Work Boots useful FOOTWEAR,MINES_FLOOR_10
16 28 Wooden Blade progression MINES_FLOOR_10,WEAPON
17 29 Iron Dirk progression MINES_FLOOR_10,WEAPON
18 30 Wind Spire progression MINES_FLOOR_10,WEAPON
19 31 Femur progression MINES_FLOOR_10,WEAPON
20 32 Steel Smallsword progression MINES_FLOOR_20,WEAPON
21 33 Wood Club progression MINES_FLOOR_20,WEAPON
22 34 Elf Blade progression MINES_FLOOR_20,WEAPON
23 35 Glow Ring useful MINES_FLOOR_20,RING
24 36 Magnet Ring useful MINES_FLOOR_20,RING
25 37 Slingshot progression WEAPON
26 38 Tundra Boots useful FOOTWEAR,MINES_FLOOR_50
27 39 Thermal Boots useful FOOTWEAR,MINES_FLOOR_50
28 40 Combat Boots useful FOOTWEAR,MINES_FLOOR_50
29 41 Silver Saber progression MINES_FLOOR_50,WEAPON
30 42 Pirate's Sword progression MINES_FLOOR_50,WEAPON
31 43 Crystal Dagger progression MINES_FLOOR_60,WEAPON
32 44 Cutlass progression MINES_FLOOR_60,WEAPON
33 45 Iron Edge progression MINES_FLOOR_60,WEAPON
34 46 Burglar's Shank progression MINES_FLOOR_60,WEAPON
35 47 Wood Mallet progression MINES_FLOOR_60,WEAPON
36 48 Master Slingshot progression WEAPON
37 49 Firewalker Boots useful FOOTWEAR,MINES_FLOOR_80
38 50 Dark Boots useful FOOTWEAR,MINES_FLOOR_80
39 51 Claymore progression MINES_FLOOR_80,WEAPON
40 52 Templar's Blade progression MINES_FLOOR_80,WEAPON
41 53 Kudgel progression MINES_FLOOR_80,WEAPON
42 54 Shadow Dagger progression MINES_FLOOR_80,WEAPON
43 55 Obsidian Edge progression MINES_FLOOR_90,WEAPON
44 56 Tempered Broadsword progression MINES_FLOOR_90,WEAPON
45 57 Wicked Kris progression MINES_FLOOR_90,WEAPON
46 58 Bone Sword progression MINES_FLOOR_90,WEAPON
47 59 Ossified Blade progression MINES_FLOOR_90,WEAPON
48 60 Space Boots useful FOOTWEAR,MINES_FLOOR_110
49 61 Crystal Shoes useful FOOTWEAR,MINES_FLOOR_110
50 62 Steel Falchion progression MINES_FLOOR_110,WEAPON
51 63 The Slammer progression MINES_FLOOR_110,WEAPON
52 64 Skull Key progression
53 65 Progressive Hoe progression PROGRESSIVE_TOOLS
54 66 Progressive Pickaxe progression PROGRESSIVE_TOOLS
55 67 Progressive Axe progression PROGRESSIVE_TOOLS
56 68 Progressive Watering Can progression PROGRESSIVE_TOOLS
57 69 Progressive Trash Can progression PROGRESSIVE_TOOLS
58 70 Progressive Fishing Rod progression PROGRESSIVE_TOOLS
59 71 Golden Scythe useful
60 72 Progressive Mine Elevator progression
61 73 Farming Level progression SKILL_LEVEL_UP
62 74 Fishing Level progression SKILL_LEVEL_UP
63 75 Foraging Level progression SKILL_LEVEL_UP
64 76 Mining Level progression SKILL_LEVEL_UP
65 77 Combat Level progression SKILL_LEVEL_UP
66 78 Earth Obelisk useful
67 79 Water Obelisk useful
68 80 Desert Obelisk progression
69 81 Island Obelisk progression
70 82 Junimo Hut useful
71 83 Gold Clock useful
72 84 Progressive Coop progression
73 85 Progressive Barn progression
74 86 Well useful
75 87 Silo progression
76 88 Mill progression
77 89 Progressive Shed progression
78 90 Fish Pond progression
79 91 Stable useful
80 92 Slime Hutch useful
81 93 Shipping Bin progression
82 94 Beach Bridge progression
83 95 Adventurer's Guild progression
84 96 Club Card progression
85 97 Magnifying Glass progression
86 98 Bear's Knowledge progression
87 99 Iridium Snake Milk progression
88 100 JotPK: Progressive Boots progression ARCADE_MACHINE_BUFFS
89 101 JotPK: Progressive Gun progression ARCADE_MACHINE_BUFFS
90 102 JotPK: Progressive Ammo progression ARCADE_MACHINE_BUFFS
91 103 JotPK: Extra Life progression ARCADE_MACHINE_BUFFS
92 104 JotPK: Increased Drop Rate progression ARCADE_MACHINE_BUFFS
93 105 Junimo Kart: Extra Life progression ARCADE_MACHINE_BUFFS
94 106 Galaxy Sword progression GALAXY_WEAPONS,WEAPON
95 107 Galaxy Dagger progression GALAXY_WEAPONS,WEAPON
96 108 Galaxy Hammer progression GALAXY_WEAPONS,WEAPON
97 109 Movement Speed Bonus useful
98 110 Luck Bonus useful
99 111 Lava Katana progression MINES_FLOOR_110,WEAPON
100 112 Progressive House progression
101 113 Traveling Merchant: Sunday progression
102 114 Traveling Merchant: Monday progression
103 115 Traveling Merchant: Tuesday progression
104 116 Traveling Merchant: Wednesday progression
105 117 Traveling Merchant: Thursday progression
106 118 Traveling Merchant: Friday progression
107 119 Traveling Merchant: Saturday progression
108 120 Traveling Merchant Stock Size progression
109 121 Traveling Merchant Discount progression
110 122 Return Scepter useful
111 5000 Resource Pack: 500 Money useful BASE_RESOURCE,RESOURCE_PACK
112 5001 Resource Pack: 1000 Money useful BASE_RESOURCE,RESOURCE_PACK
113 5002 Resource Pack: 1500 Money useful BASE_RESOURCE,RESOURCE_PACK
114 5003 Resource Pack: 2000 Money useful BASE_RESOURCE,RESOURCE_PACK
115 5004 Resource Pack: 25 Stone filler BASE_RESOURCE,RESOURCE_PACK
116 5005 Resource Pack: 50 Stone filler BASE_RESOURCE,RESOURCE_PACK
117 5006 Resource Pack: 75 Stone filler BASE_RESOURCE,RESOURCE_PACK
118 5007 Resource Pack: 100 Stone filler BASE_RESOURCE,RESOURCE_PACK
119 5008 Resource Pack: 25 Wood filler BASE_RESOURCE,RESOURCE_PACK
120 5009 Resource Pack: 50 Wood filler BASE_RESOURCE,RESOURCE_PACK
121 5010 Resource Pack: 75 Wood filler BASE_RESOURCE,RESOURCE_PACK
122 5011 Resource Pack: 100 Wood filler BASE_RESOURCE,RESOURCE_PACK
123 5012 Resource Pack: 5 Hardwood useful BASE_RESOURCE,RESOURCE_PACK
124 5013 Resource Pack: 10 Hardwood useful BASE_RESOURCE,RESOURCE_PACK
125 5014 Resource Pack: 15 Hardwood useful BASE_RESOURCE,RESOURCE_PACK
126 5015 Resource Pack: 20 Hardwood useful BASE_RESOURCE,RESOURCE_PACK
127 5016 Resource Pack: 15 Fiber filler BASE_RESOURCE,RESOURCE_PACK
128 5017 Resource Pack: 30 Fiber filler BASE_RESOURCE,RESOURCE_PACK
129 5018 Resource Pack: 45 Fiber filler BASE_RESOURCE,RESOURCE_PACK
130 5019 Resource Pack: 60 Fiber filler BASE_RESOURCE,RESOURCE_PACK
131 5020 Resource Pack: 5 Coal filler BASE_RESOURCE,RESOURCE_PACK
132 5021 Resource Pack: 10 Coal filler BASE_RESOURCE,RESOURCE_PACK
133 5022 Resource Pack: 15 Coal filler BASE_RESOURCE,RESOURCE_PACK
134 5023 Resource Pack: 20 Coal filler BASE_RESOURCE,RESOURCE_PACK
135 5024 Resource Pack: 5 Clay filler BASE_RESOURCE,RESOURCE_PACK
136 5025 Resource Pack: 10 Clay filler BASE_RESOURCE,RESOURCE_PACK
137 5026 Resource Pack: 15 Clay filler BASE_RESOURCE,RESOURCE_PACK
138 5027 Resource Pack: 20 Clay filler BASE_RESOURCE,RESOURCE_PACK
139 5028 Resource Pack: 1 Warp Totem: Beach filler RESOURCE_PACK,WARP_TOTEM
140 5029 Resource Pack: 3 Warp Totem: Beach filler RESOURCE_PACK,WARP_TOTEM
141 5030 Resource Pack: 5 Warp Totem: Beach filler RESOURCE_PACK,WARP_TOTEM
142 5031 Resource Pack: 7 Warp Totem: Beach filler RESOURCE_PACK,WARP_TOTEM
143 5032 Resource Pack: 9 Warp Totem: Beach filler RESOURCE_PACK,WARP_TOTEM
144 5033 Resource Pack: 10 Warp Totem: Beach filler RESOURCE_PACK,WARP_TOTEM
145 5034 Resource Pack: 1 Warp Totem: Desert filler RESOURCE_PACK,WARP_TOTEM
146 5035 Resource Pack: 3 Warp Totem: Desert filler RESOURCE_PACK,WARP_TOTEM
147 5036 Resource Pack: 5 Warp Totem: Desert filler RESOURCE_PACK,WARP_TOTEM
148 5037 Resource Pack: 7 Warp Totem: Desert filler RESOURCE_PACK,WARP_TOTEM
149 5038 Resource Pack: 9 Warp Totem: Desert filler RESOURCE_PACK,WARP_TOTEM
150 5039 Resource Pack: 10 Warp Totem: Desert filler RESOURCE_PACK,WARP_TOTEM
151 5040 Resource Pack: 1 Warp Totem: Farm filler RESOURCE_PACK,WARP_TOTEM
152 5041 Resource Pack: 3 Warp Totem: Farm filler RESOURCE_PACK,WARP_TOTEM
153 5042 Resource Pack: 5 Warp Totem: Farm filler RESOURCE_PACK,WARP_TOTEM
154 5043 Resource Pack: 7 Warp Totem: Farm filler RESOURCE_PACK,WARP_TOTEM
155 5044 Resource Pack: 9 Warp Totem: Farm filler RESOURCE_PACK,WARP_TOTEM
156 5045 Resource Pack: 10 Warp Totem: Farm filler RESOURCE_PACK,WARP_TOTEM
157 5046 Resource Pack: 1 Warp Totem: Island filler RESOURCE_PACK,WARP_TOTEM
158 5047 Resource Pack: 3 Warp Totem: Island filler RESOURCE_PACK,WARP_TOTEM
159 5048 Resource Pack: 5 Warp Totem: Island filler RESOURCE_PACK,WARP_TOTEM
160 5049 Resource Pack: 7 Warp Totem: Island filler RESOURCE_PACK,WARP_TOTEM
161 5050 Resource Pack: 9 Warp Totem: Island filler RESOURCE_PACK,WARP_TOTEM
162 5051 Resource Pack: 10 Warp Totem: Island filler RESOURCE_PACK,WARP_TOTEM
163 5052 Resource Pack: 1 Warp Totem: Mountains filler RESOURCE_PACK,WARP_TOTEM
164 5053 Resource Pack: 3 Warp Totem: Mountains filler RESOURCE_PACK,WARP_TOTEM
165 5054 Resource Pack: 5 Warp Totem: Mountains filler RESOURCE_PACK,WARP_TOTEM
166 5055 Resource Pack: 7 Warp Totem: Mountains filler RESOURCE_PACK,WARP_TOTEM
167 5056 Resource Pack: 9 Warp Totem: Mountains filler RESOURCE_PACK,WARP_TOTEM
168 5057 Resource Pack: 10 Warp Totem: Mountains filler RESOURCE_PACK,WARP_TOTEM
169 5058 Resource Pack: 6 Geode filler GEODE,RESOURCE_PACK
170 5059 Resource Pack: 12 Geode filler GEODE,RESOURCE_PACK
171 5060 Resource Pack: 18 Geode filler GEODE,RESOURCE_PACK
172 5061 Resource Pack: 24 Geode filler GEODE,RESOURCE_PACK
173 5062 Resource Pack: 4 Frozen Geode filler GEODE,RESOURCE_PACK
174 5063 Resource Pack: 8 Frozen Geode filler GEODE,RESOURCE_PACK
175 5064 Resource Pack: 12 Frozen Geode filler GEODE,RESOURCE_PACK
176 5065 Resource Pack: 16 Frozen Geode filler GEODE,RESOURCE_PACK
177 5066 Resource Pack: 3 Magma Geode filler GEODE,RESOURCE_PACK
178 5067 Resource Pack: 6 Magma Geode filler GEODE,RESOURCE_PACK
179 5068 Resource Pack: 9 Magma Geode filler GEODE,RESOURCE_PACK
180 5069 Resource Pack: 12 Magma Geode filler GEODE,RESOURCE_PACK
181 5070 Resource Pack: 2 Omni Geode useful GEODE,RESOURCE_PACK
182 5071 Resource Pack: 4 Omni Geode useful GEODE,RESOURCE_PACK
183 5072 Resource Pack: 6 Omni Geode useful GEODE,RESOURCE_PACK
184 5073 Resource Pack: 8 Omni Geode useful GEODE,RESOURCE_PACK
185 5074 Resource Pack: 25 Copper Ore filler ORE,RESOURCE_PACK
186 5075 Resource Pack: 50 Copper Ore filler ORE,RESOURCE_PACK
187 5076 Resource Pack: 75 Copper Ore filler ORE,RESOURCE_PACK
188 5077 Resource Pack: 100 Copper Ore filler ORE,RESOURCE_PACK
189 5078 Resource Pack: 125 Copper Ore filler ORE,RESOURCE_PACK
190 5079 Resource Pack: 150 Copper Ore filler ORE,RESOURCE_PACK
191 5080 Resource Pack: 25 Iron Ore filler ORE,RESOURCE_PACK
192 5081 Resource Pack: 50 Iron Ore filler ORE,RESOURCE_PACK
193 5082 Resource Pack: 75 Iron Ore filler ORE,RESOURCE_PACK
194 5083 Resource Pack: 100 Iron Ore filler ORE,RESOURCE_PACK
195 5084 Resource Pack: 12 Gold Ore useful ORE,RESOURCE_PACK
196 5085 Resource Pack: 25 Gold Ore useful ORE,RESOURCE_PACK
197 5086 Resource Pack: 38 Gold Ore useful ORE,RESOURCE_PACK
198 5087 Resource Pack: 50 Gold Ore useful ORE,RESOURCE_PACK
199 5088 Resource Pack: 5 Iridium Ore useful ORE,RESOURCE_PACK
200 5089 Resource Pack: 10 Iridium Ore useful ORE,RESOURCE_PACK
201 5090 Resource Pack: 15 Iridium Ore useful ORE,RESOURCE_PACK
202 5091 Resource Pack: 20 Iridium Ore useful ORE,RESOURCE_PACK
203 5092 Resource Pack: 5 Quartz filler ORE,RESOURCE_PACK
204 5093 Resource Pack: 10 Quartz filler ORE,RESOURCE_PACK
205 5094 Resource Pack: 15 Quartz filler ORE,RESOURCE_PACK
206 5095 Resource Pack: 20 Quartz filler ORE,RESOURCE_PACK
207 5096 Resource Pack: 10 Basic Fertilizer filler FERTILIZER,RESOURCE_PACK
208 5097 Resource Pack: 20 Basic Fertilizer filler FERTILIZER,RESOURCE_PACK
209 5098 Resource Pack: 30 Basic Fertilizer filler FERTILIZER,RESOURCE_PACK
210 5099 Resource Pack: 40 Basic Fertilizer filler FERTILIZER,RESOURCE_PACK
211 5100 Resource Pack: 50 Basic Fertilizer filler FERTILIZER,RESOURCE_PACK
212 5101 Resource Pack: 60 Basic Fertilizer filler FERTILIZER,RESOURCE_PACK
213 5102 Resource Pack: 10 Basic Retaining Soil filler FERTILIZER,RESOURCE_PACK
214 5103 Resource Pack: 20 Basic Retaining Soil filler FERTILIZER,RESOURCE_PACK
215 5104 Resource Pack: 30 Basic Retaining Soil filler FERTILIZER,RESOURCE_PACK
216 5105 Resource Pack: 40 Basic Retaining Soil filler FERTILIZER,RESOURCE_PACK
217 5106 Resource Pack: 50 Basic Retaining Soil filler FERTILIZER,RESOURCE_PACK
218 5107 Resource Pack: 60 Basic Retaining Soil filler FERTILIZER,RESOURCE_PACK
219 5108 Resource Pack: 10 Speed-Gro filler FERTILIZER,RESOURCE_PACK
220 5109 Resource Pack: 20 Speed-Gro filler FERTILIZER,RESOURCE_PACK
221 5110 Resource Pack: 30 Speed-Gro filler FERTILIZER,RESOURCE_PACK
222 5111 Resource Pack: 40 Speed-Gro filler FERTILIZER,RESOURCE_PACK
223 5112 Resource Pack: 50 Speed-Gro filler FERTILIZER,RESOURCE_PACK
224 5113 Resource Pack: 60 Speed-Gro filler FERTILIZER,RESOURCE_PACK
225 5114 Resource Pack: 4 Quality Fertilizer filler FERTILIZER,RESOURCE_PACK
226 5115 Resource Pack: 12 Quality Fertilizer filler FERTILIZER,RESOURCE_PACK
227 5116 Resource Pack: 20 Quality Fertilizer filler FERTILIZER,RESOURCE_PACK
228 5117 Resource Pack: 28 Quality Fertilizer filler FERTILIZER,RESOURCE_PACK
229 5118 Resource Pack: 36 Quality Fertilizer filler FERTILIZER,RESOURCE_PACK
230 5119 Resource Pack: 40 Quality Fertilizer filler FERTILIZER,RESOURCE_PACK
231 5120 Resource Pack: 4 Quality Retaining Soil filler FERTILIZER,RESOURCE_PACK
232 5121 Resource Pack: 12 Quality Retaining Soil filler FERTILIZER,RESOURCE_PACK
233 5122 Resource Pack: 20 Quality Retaining Soil filler FERTILIZER,RESOURCE_PACK
234 5123 Resource Pack: 28 Quality Retaining Soil filler FERTILIZER,RESOURCE_PACK
235 5124 Resource Pack: 36 Quality Retaining Soil filler FERTILIZER,RESOURCE_PACK
236 5125 Resource Pack: 40 Quality Retaining Soil filler FERTILIZER,RESOURCE_PACK
237 5126 Resource Pack: 4 Deluxe Speed-Gro filler FERTILIZER,RESOURCE_PACK
238 5127 Resource Pack: 12 Deluxe Speed-Gro filler FERTILIZER,RESOURCE_PACK
239 5128 Resource Pack: 20 Deluxe Speed-Gro filler FERTILIZER,RESOURCE_PACK
240 5129 Resource Pack: 28 Deluxe Speed-Gro filler FERTILIZER,RESOURCE_PACK
241 5130 Resource Pack: 36 Deluxe Speed-Gro filler FERTILIZER,RESOURCE_PACK
242 5131 Resource Pack: 40 Deluxe Speed-Gro filler FERTILIZER,RESOURCE_PACK
243 5132 Resource Pack: 2 Deluxe Fertilizer useful FERTILIZER,RESOURCE_PACK
244 5133 Resource Pack: 6 Deluxe Fertilizer useful FERTILIZER,RESOURCE_PACK
245 5134 Resource Pack: 10 Deluxe Fertilizer useful FERTILIZER,RESOURCE_PACK
246 5135 Resource Pack: 14 Deluxe Fertilizer useful FERTILIZER,RESOURCE_PACK
247 5136 Resource Pack: 18 Deluxe Fertilizer useful FERTILIZER,RESOURCE_PACK
248 5137 Resource Pack: 20 Deluxe Fertilizer useful FERTILIZER,RESOURCE_PACK
249 5138 Resource Pack: 2 Deluxe Retaining Soil useful FERTILIZER,RESOURCE_PACK
250 5139 Resource Pack: 6 Deluxe Retaining Soil useful FERTILIZER,RESOURCE_PACK
251 5140 Resource Pack: 10 Deluxe Retaining Soil useful FERTILIZER,RESOURCE_PACK
252 5141 Resource Pack: 14 Deluxe Retaining Soil useful FERTILIZER,RESOURCE_PACK
253 5142 Resource Pack: 18 Deluxe Retaining Soil useful FERTILIZER,RESOURCE_PACK
254 5143 Resource Pack: 20 Deluxe Retaining Soil useful FERTILIZER,RESOURCE_PACK
255 5144 Resource Pack: 2 Hyper Speed-Gro useful FERTILIZER,RESOURCE_PACK
256 5145 Resource Pack: 6 Hyper Speed-Gro useful FERTILIZER,RESOURCE_PACK
257 5146 Resource Pack: 10 Hyper Speed-Gro useful FERTILIZER,RESOURCE_PACK
258 5147 Resource Pack: 14 Hyper Speed-Gro useful FERTILIZER,RESOURCE_PACK
259 5148 Resource Pack: 18 Hyper Speed-Gro useful FERTILIZER,RESOURCE_PACK
260 5149 Resource Pack: 20 Hyper Speed-Gro useful FERTILIZER,RESOURCE_PACK
261 5150 Resource Pack: 2 Tree Fertilizer filler FERTILIZER,RESOURCE_PACK
262 5151 Resource Pack: 6 Tree Fertilizer filler FERTILIZER,RESOURCE_PACK
263 5152 Resource Pack: 10 Tree Fertilizer filler FERTILIZER,RESOURCE_PACK
264 5153 Resource Pack: 14 Tree Fertilizer filler FERTILIZER,RESOURCE_PACK
265 5154 Resource Pack: 18 Tree Fertilizer filler FERTILIZER,RESOURCE_PACK
266 5155 Resource Pack: 20 Tree Fertilizer filler FERTILIZER,RESOURCE_PACK
267 5156 Resource Pack: 10 Spring Seeds filler RESOURCE_PACK,SEED
268 5157 Resource Pack: 20 Spring Seeds filler RESOURCE_PACK,SEED
269 5158 Resource Pack: 30 Spring Seeds filler RESOURCE_PACK,SEED
270 5159 Resource Pack: 40 Spring Seeds filler RESOURCE_PACK,SEED
271 5160 Resource Pack: 50 Spring Seeds filler RESOURCE_PACK,SEED
272 5161 Resource Pack: 60 Spring Seeds filler RESOURCE_PACK,SEED
273 5162 Resource Pack: 10 Summer Seeds filler RESOURCE_PACK,SEED
274 5163 Resource Pack: 20 Summer Seeds filler RESOURCE_PACK,SEED
275 5164 Resource Pack: 30 Summer Seeds filler RESOURCE_PACK,SEED
276 5165 Resource Pack: 40 Summer Seeds filler RESOURCE_PACK,SEED
277 5166 Resource Pack: 50 Summer Seeds filler RESOURCE_PACK,SEED
278 5167 Resource Pack: 60 Summer Seeds filler RESOURCE_PACK,SEED
279 5168 Resource Pack: 10 Fall Seeds filler RESOURCE_PACK,SEED
280 5169 Resource Pack: 20 Fall Seeds filler RESOURCE_PACK,SEED
281 5170 Resource Pack: 30 Fall Seeds filler RESOURCE_PACK,SEED
282 5171 Resource Pack: 40 Fall Seeds filler RESOURCE_PACK,SEED
283 5172 Resource Pack: 50 Fall Seeds filler RESOURCE_PACK,SEED
284 5173 Resource Pack: 60 Fall Seeds filler RESOURCE_PACK,SEED
285 5174 Resource Pack: 10 Winter Seeds filler RESOURCE_PACK,SEED
286 5175 Resource Pack: 20 Winter Seeds filler RESOURCE_PACK,SEED
287 5176 Resource Pack: 30 Winter Seeds filler RESOURCE_PACK,SEED
288 5177 Resource Pack: 40 Winter Seeds filler RESOURCE_PACK,SEED
289 5178 Resource Pack: 50 Winter Seeds filler RESOURCE_PACK,SEED
290 5179 Resource Pack: 60 Winter Seeds filler RESOURCE_PACK,SEED
291 5180 Resource Pack: 1 Mahogany Seed filler RESOURCE_PACK,SEED
292 5181 Resource Pack: 3 Mahogany Seed filler RESOURCE_PACK,SEED
293 5182 Resource Pack: 5 Mahogany Seed filler RESOURCE_PACK,SEED
294 5183 Resource Pack: 7 Mahogany Seed filler RESOURCE_PACK,SEED
295 5184 Resource Pack: 9 Mahogany Seed filler RESOURCE_PACK,SEED
296 5185 Resource Pack: 10 Mahogany Seed filler RESOURCE_PACK,SEED
297 5186 Resource Pack: 10 Bait filler FISHING_RESOURCE,RESOURCE_PACK
298 5187 Resource Pack: 20 Bait filler FISHING_RESOURCE,RESOURCE_PACK
299 5188 Resource Pack: 30 Bait filler FISHING_RESOURCE,RESOURCE_PACK
300 5189 Resource Pack: 40 Bait filler FISHING_RESOURCE,RESOURCE_PACK
301 5190 Resource Pack: 50 Bait filler FISHING_RESOURCE,RESOURCE_PACK
302 5191 Resource Pack: 60 Bait filler FISHING_RESOURCE,RESOURCE_PACK
303 5192 Resource Pack: 1 Crab Pot filler FISHING_RESOURCE,RESOURCE_PACK
304 5193 Resource Pack: 2 Crab Pot filler FISHING_RESOURCE,RESOURCE_PACK
305 5194 Resource Pack: 3 Crab Pot filler FISHING_RESOURCE,RESOURCE_PACK
306 5195 Resource Pack: 4 Crab Pot filler FISHING_RESOURCE,RESOURCE_PACK
307 5196 Resource Pack: 5 Crab Pot filler FISHING_RESOURCE,RESOURCE_PACK
308 5197 Resource Pack: 6 Crab Pot filler FISHING_RESOURCE,RESOURCE_PACK
309 5198 Friendship Bonus (1 <3) useful FRIENDSHIP_PACK
310 5199 Friendship Bonus (2 <3) useful FRIENDSHIP_PACK
311 5200 Friendship Bonus (3 <3) useful FRIENDSHIP_PACK
312 5201 Friendship Bonus (4 <3) useful FRIENDSHIP_PACK

View File

@@ -0,0 +1,379 @@
id,region,name,tags
1,Crafts Room,Spring Foraging Bundle,"BUNDLE,COMMUNITY_CENTER_BUNDLE,CRAFTS_ROOM_BUNDLE,MANDATORY"
2,Crafts Room,Summer Foraging Bundle,"BUNDLE,COMMUNITY_CENTER_BUNDLE,CRAFTS_ROOM_BUNDLE,MANDATORY"
3,Crafts Room,Fall Foraging Bundle,"BUNDLE,COMMUNITY_CENTER_BUNDLE,CRAFTS_ROOM_BUNDLE,MANDATORY"
4,Crafts Room,Winter Foraging Bundle,"BUNDLE,COMMUNITY_CENTER_BUNDLE,CRAFTS_ROOM_BUNDLE,MANDATORY"
5,Crafts Room,Construction Bundle,"BUNDLE,COMMUNITY_CENTER_BUNDLE,CRAFTS_ROOM_BUNDLE,MANDATORY"
6,Crafts Room,Exotic Foraging Bundle,"BUNDLE,COMMUNITY_CENTER_BUNDLE,CRAFTS_ROOM_BUNDLE,MANDATORY"
7,Pantry,Spring Crops Bundle,"BUNDLE,COMMUNITY_CENTER_BUNDLE,MANDATORY,PANTRY_BUNDLE"
8,Pantry,Summer Crops Bundle,"BUNDLE,COMMUNITY_CENTER_BUNDLE,MANDATORY,PANTRY_BUNDLE"
9,Pantry,Fall Crops Bundle,"BUNDLE,COMMUNITY_CENTER_BUNDLE,MANDATORY,PANTRY_BUNDLE"
10,Pantry,Quality Crops Bundle,"BUNDLE,COMMUNITY_CENTER_BUNDLE,MANDATORY,PANTRY_BUNDLE"
11,Pantry,Animal Bundle,"BUNDLE,COMMUNITY_CENTER_BUNDLE,MANDATORY,PANTRY_BUNDLE"
12,Pantry,Artisan Bundle,"BUNDLE,COMMUNITY_CENTER_BUNDLE,MANDATORY,PANTRY_BUNDLE"
13,Fish Tank,River Fish Bundle,"BUNDLE,COMMUNITY_CENTER_BUNDLE,FISH_TANK_BUNDLE,MANDATORY"
14,Fish Tank,Lake Fish Bundle,"BUNDLE,COMMUNITY_CENTER_BUNDLE,FISH_TANK_BUNDLE,MANDATORY"
15,Fish Tank,Ocean Fish Bundle,"BUNDLE,COMMUNITY_CENTER_BUNDLE,FISH_TANK_BUNDLE,MANDATORY"
16,Fish Tank,Night Fishing Bundle,"BUNDLE,COMMUNITY_CENTER_BUNDLE,FISH_TANK_BUNDLE,MANDATORY"
17,Fish Tank,Crab Pot Bundle,"BUNDLE,COMMUNITY_CENTER_BUNDLE,FISH_TANK_BUNDLE,MANDATORY"
18,Fish Tank,Specialty Fish Bundle,"BUNDLE,COMMUNITY_CENTER_BUNDLE,FISH_TANK_BUNDLE,MANDATORY"
19,Boiler Room,Blacksmith's Bundle,"BOILER_ROOM_BUNDLE,BUNDLE,COMMUNITY_CENTER_BUNDLE,MANDATORY"
20,Boiler Room,Geologist's Bundle,"BOILER_ROOM_BUNDLE,BUNDLE,COMMUNITY_CENTER_BUNDLE,MANDATORY"
21,Boiler Room,Adventurer's Bundle,"BOILER_ROOM_BUNDLE,BUNDLE,COMMUNITY_CENTER_BUNDLE,MANDATORY"
22,Bulletin Board,Chef's Bundle,"BULLETIN_BOARD_BUNDLE,BUNDLE,COMMUNITY_CENTER_BUNDLE,MANDATORY"
23,Bulletin Board,Dye Bundle,"BULLETIN_BOARD_BUNDLE,BUNDLE,COMMUNITY_CENTER_BUNDLE,MANDATORY"
24,Bulletin Board,Field Research Bundle,"BULLETIN_BOARD_BUNDLE,BUNDLE,COMMUNITY_CENTER_BUNDLE,MANDATORY"
25,Bulletin Board,Fodder Bundle,"BULLETIN_BOARD_BUNDLE,BUNDLE,COMMUNITY_CENTER_BUNDLE,MANDATORY"
26,Bulletin Board,Enchanter's Bundle,"BULLETIN_BOARD_BUNDLE,BUNDLE,COMMUNITY_CENTER_BUNDLE,MANDATORY"
27,Vault,"2,500g Bundle","BUNDLE,COMMUNITY_CENTER_BUNDLE,MANDATORY,VAULT_BUNDLE"
28,Vault,"5,000g Bundle","BUNDLE,COMMUNITY_CENTER_BUNDLE,MANDATORY,VAULT_BUNDLE"
29,Vault,"10,000g Bundle","BUNDLE,COMMUNITY_CENTER_BUNDLE,MANDATORY,VAULT_BUNDLE"
30,Vault,"25,000g Bundle","BUNDLE,COMMUNITY_CENTER_BUNDLE,MANDATORY,VAULT_BUNDLE"
31,Abandoned JojaMart,The Missing Bundle,BUNDLE
32,Crafts Room,Complete Crafts Room,"COMMUNITY_CENTER_ROOM,MANDATORY"
33,Pantry,Complete Pantry,"COMMUNITY_CENTER_ROOM,MANDATORY"
34,Fish Tank,Complete Fish Tank,"COMMUNITY_CENTER_ROOM,MANDATORY"
35,Boiler Room,Complete Boiler Room,"COMMUNITY_CENTER_ROOM,MANDATORY"
36,Bulletin Board,Complete Bulletin Board,"COMMUNITY_CENTER_ROOM,MANDATORY"
37,Vault,Complete Vault,"COMMUNITY_CENTER_ROOM,MANDATORY"
101,Pierre's General Store,Large Pack,BACKPACK
102,Pierre's General Store,Deluxe Pack,BACKPACK
103,Clint's Blacksmith,Copper Hoe Upgrade,"HOE_UPGRADE,TOOL_UPGRADE"
104,Clint's Blacksmith,Iron Hoe Upgrade,"HOE_UPGRADE,TOOL_UPGRADE"
105,Clint's Blacksmith,Gold Hoe Upgrade,"HOE_UPGRADE,TOOL_UPGRADE"
106,Clint's Blacksmith,Iridium Hoe Upgrade,"HOE_UPGRADE,TOOL_UPGRADE"
107,Clint's Blacksmith,Copper Pickaxe Upgrade,"PICKAXE_UPGRADE,TOOL_UPGRADE"
108,Clint's Blacksmith,Iron Pickaxe Upgrade,"PICKAXE_UPGRADE,TOOL_UPGRADE"
109,Clint's Blacksmith,Gold Pickaxe Upgrade,"PICKAXE_UPGRADE,TOOL_UPGRADE"
110,Clint's Blacksmith,Iridium Pickaxe Upgrade,"PICKAXE_UPGRADE,TOOL_UPGRADE"
111,Clint's Blacksmith,Copper Axe Upgrade,"AXE_UPGRADE,TOOL_UPGRADE"
112,Clint's Blacksmith,Iron Axe Upgrade,"AXE_UPGRADE,TOOL_UPGRADE"
113,Clint's Blacksmith,Gold Axe Upgrade,"AXE_UPGRADE,TOOL_UPGRADE"
114,Clint's Blacksmith,Iridium Axe Upgrade,"AXE_UPGRADE,TOOL_UPGRADE"
115,Clint's Blacksmith,Copper Watering Can Upgrade,"TOOL_UPGRADE,WATERING_CAN_UPGRADE"
116,Clint's Blacksmith,Iron Watering Can Upgrade,"TOOL_UPGRADE,WATERING_CAN_UPGRADE"
117,Clint's Blacksmith,Gold Watering Can Upgrade,"TOOL_UPGRADE,WATERING_CAN_UPGRADE"
118,Clint's Blacksmith,Iridium Watering Can Upgrade,"TOOL_UPGRADE,WATERING_CAN_UPGRADE"
119,Clint's Blacksmith,Copper Trash Can Upgrade,"TOOL_UPGRADE,TRASH_CAN_UPGRADE"
120,Clint's Blacksmith,Iron Trash Can Upgrade,"TOOL_UPGRADE,TRASH_CAN_UPGRADE"
121,Clint's Blacksmith,Gold Trash Can Upgrade,"TOOL_UPGRADE,TRASH_CAN_UPGRADE"
122,Clint's Blacksmith,Iridium Trash Can Upgrade,"TOOL_UPGRADE,TRASH_CAN_UPGRADE"
123,Willy's Fish Shop,Purchase Training Rod,"FISHING_ROD_UPGRADE,TOOL_UPGRADE"
124,Stardew Valley,Bamboo Pole Cutscene,"FISHING_ROD_UPGRADE,TOOL_UPGRADE"
125,Willy's Fish Shop,Purchase Fiberglass Rod,"FISHING_ROD_UPGRADE,TOOL_UPGRADE"
126,Willy's Fish Shop,Purchase Iridium Rod,"FISHING_ROD_UPGRADE,TOOL_UPGRADE"
201,The Mines - Floor 10,The Mines Floor 10 Treasure,"MANDATORY,THE_MINES_TREASURE"
202,The Mines - Floor 20,The Mines Floor 20 Treasure,"MANDATORY,THE_MINES_TREASURE"
203,The Mines - Floor 40,The Mines Floor 40 Treasure,"MANDATORY,THE_MINES_TREASURE"
204,The Mines - Floor 50,The Mines Floor 50 Treasure,"MANDATORY,THE_MINES_TREASURE"
205,The Mines - Floor 60,The Mines Floor 60 Treasure,"MANDATORY,THE_MINES_TREASURE"
206,The Mines - Floor 70,The Mines Floor 70 Treasure,"MANDATORY,THE_MINES_TREASURE"
207,The Mines - Floor 80,The Mines Floor 80 Treasure,"MANDATORY,THE_MINES_TREASURE"
208,The Mines - Floor 90,The Mines Floor 90 Treasure,"MANDATORY,THE_MINES_TREASURE"
209,The Mines - Floor 100,The Mines Floor 100 Treasure,"MANDATORY,THE_MINES_TREASURE"
210,The Mines - Floor 110,The Mines Floor 110 Treasure,"MANDATORY,THE_MINES_TREASURE"
211,The Mines - Floor 120,The Mines Floor 120 Treasure,"MANDATORY,THE_MINES_TREASURE"
212,Quarry Mine,Grim Reaper statue,MANDATORY
213,The Mines,The Mines Entrance Cutscene,MANDATORY
214,The Mines - Floor 5,Floor 5 Elevator,THE_MINES_ELEVATOR
215,The Mines - Floor 10,Floor 10 Elevator,THE_MINES_ELEVATOR
216,The Mines - Floor 15,Floor 15 Elevator,THE_MINES_ELEVATOR
217,The Mines - Floor 20,Floor 20 Elevator,THE_MINES_ELEVATOR
218,The Mines - Floor 25,Floor 25 Elevator,THE_MINES_ELEVATOR
219,The Mines - Floor 30,Floor 30 Elevator,THE_MINES_ELEVATOR
220,The Mines - Floor 35,Floor 35 Elevator,THE_MINES_ELEVATOR
221,The Mines - Floor 40,Floor 40 Elevator,THE_MINES_ELEVATOR
222,The Mines - Floor 45,Floor 45 Elevator,THE_MINES_ELEVATOR
223,The Mines - Floor 50,Floor 50 Elevator,THE_MINES_ELEVATOR
224,The Mines - Floor 55,Floor 55 Elevator,THE_MINES_ELEVATOR
225,The Mines - Floor 60,Floor 60 Elevator,THE_MINES_ELEVATOR
226,The Mines - Floor 65,Floor 65 Elevator,THE_MINES_ELEVATOR
227,The Mines - Floor 70,Floor 70 Elevator,THE_MINES_ELEVATOR
228,The Mines - Floor 75,Floor 75 Elevator,THE_MINES_ELEVATOR
229,The Mines - Floor 80,Floor 80 Elevator,THE_MINES_ELEVATOR
230,The Mines - Floor 85,Floor 85 Elevator,THE_MINES_ELEVATOR
231,The Mines - Floor 90,Floor 90 Elevator,THE_MINES_ELEVATOR
232,The Mines - Floor 95,Floor 95 Elevator,THE_MINES_ELEVATOR
233,The Mines - Floor 100,Floor 100 Elevator,THE_MINES_ELEVATOR
234,The Mines - Floor 105,Floor 105 Elevator,THE_MINES_ELEVATOR
235,The Mines - Floor 110,Floor 110 Elevator,THE_MINES_ELEVATOR
236,The Mines - Floor 115,Floor 115 Elevator,THE_MINES_ELEVATOR
237,The Mines - Floor 120,Floor 120 Elevator,THE_MINES_ELEVATOR
301,Stardew Valley,Level 1 Farming,"FARMING_LEVEL,SKILL_LEVEL"
302,Stardew Valley,Level 2 Farming,"FARMING_LEVEL,SKILL_LEVEL"
303,Stardew Valley,Level 3 Farming,"FARMING_LEVEL,SKILL_LEVEL"
304,Stardew Valley,Level 4 Farming,"FARMING_LEVEL,SKILL_LEVEL"
305,Stardew Valley,Level 5 Farming,"FARMING_LEVEL,SKILL_LEVEL"
306,Stardew Valley,Level 6 Farming,"FARMING_LEVEL,SKILL_LEVEL"
307,Stardew Valley,Level 7 Farming,"FARMING_LEVEL,SKILL_LEVEL"
308,Stardew Valley,Level 8 Farming,"FARMING_LEVEL,SKILL_LEVEL"
309,Stardew Valley,Level 9 Farming,"FARMING_LEVEL,SKILL_LEVEL"
310,Stardew Valley,Level 10 Farming,"FARMING_LEVEL,SKILL_LEVEL"
311,Stardew Valley,Level 1 Fishing,"FISHING_LEVEL,SKILL_LEVEL"
312,Stardew Valley,Level 2 Fishing,"FISHING_LEVEL,SKILL_LEVEL"
313,Stardew Valley,Level 3 Fishing,"FISHING_LEVEL,SKILL_LEVEL"
314,Stardew Valley,Level 4 Fishing,"FISHING_LEVEL,SKILL_LEVEL"
315,Stardew Valley,Level 5 Fishing,"FISHING_LEVEL,SKILL_LEVEL"
316,Stardew Valley,Level 6 Fishing,"FISHING_LEVEL,SKILL_LEVEL"
317,Stardew Valley,Level 7 Fishing,"FISHING_LEVEL,SKILL_LEVEL"
318,Stardew Valley,Level 8 Fishing,"FISHING_LEVEL,SKILL_LEVEL"
319,Stardew Valley,Level 9 Fishing,"FISHING_LEVEL,SKILL_LEVEL"
320,Stardew Valley,Level 10 Fishing,"FISHING_LEVEL,SKILL_LEVEL"
321,Stardew Valley,Level 1 Foraging,"FORAGING_LEVEL,SKILL_LEVEL"
322,Stardew Valley,Level 2 Foraging,"FORAGING_LEVEL,SKILL_LEVEL"
323,Stardew Valley,Level 3 Foraging,"FORAGING_LEVEL,SKILL_LEVEL"
324,Stardew Valley,Level 4 Foraging,"FORAGING_LEVEL,SKILL_LEVEL"
325,Stardew Valley,Level 5 Foraging,"FORAGING_LEVEL,SKILL_LEVEL"
326,Stardew Valley,Level 6 Foraging,"FORAGING_LEVEL,SKILL_LEVEL"
327,Stardew Valley,Level 7 Foraging,"FORAGING_LEVEL,SKILL_LEVEL"
328,Stardew Valley,Level 8 Foraging,"FORAGING_LEVEL,SKILL_LEVEL"
329,Stardew Valley,Level 9 Foraging,"FORAGING_LEVEL,SKILL_LEVEL"
330,Stardew Valley,Level 10 Foraging,"FORAGING_LEVEL,SKILL_LEVEL"
331,Stardew Valley,Level 1 Mining,"MINING_LEVEL,SKILL_LEVEL"
332,Stardew Valley,Level 2 Mining,"MINING_LEVEL,SKILL_LEVEL"
333,Stardew Valley,Level 3 Mining,"MINING_LEVEL,SKILL_LEVEL"
334,Stardew Valley,Level 4 Mining,"MINING_LEVEL,SKILL_LEVEL"
335,Stardew Valley,Level 5 Mining,"MINING_LEVEL,SKILL_LEVEL"
336,Stardew Valley,Level 6 Mining,"MINING_LEVEL,SKILL_LEVEL"
337,Stardew Valley,Level 7 Mining,"MINING_LEVEL,SKILL_LEVEL"
338,Stardew Valley,Level 8 Mining,"MINING_LEVEL,SKILL_LEVEL"
339,Stardew Valley,Level 9 Mining,"MINING_LEVEL,SKILL_LEVEL"
340,Stardew Valley,Level 10 Mining,"MINING_LEVEL,SKILL_LEVEL"
341,Stardew Valley,Level 1 Combat,"COMBAT_LEVEL,SKILL_LEVEL"
342,Stardew Valley,Level 2 Combat,"COMBAT_LEVEL,SKILL_LEVEL"
343,Stardew Valley,Level 3 Combat,"COMBAT_LEVEL,SKILL_LEVEL"
344,Stardew Valley,Level 4 Combat,"COMBAT_LEVEL,SKILL_LEVEL"
345,Stardew Valley,Level 5 Combat,"COMBAT_LEVEL,SKILL_LEVEL"
346,Stardew Valley,Level 6 Combat,"COMBAT_LEVEL,SKILL_LEVEL"
347,Stardew Valley,Level 7 Combat,"COMBAT_LEVEL,SKILL_LEVEL"
348,Stardew Valley,Level 8 Combat,"COMBAT_LEVEL,SKILL_LEVEL"
349,Stardew Valley,Level 9 Combat,"COMBAT_LEVEL,SKILL_LEVEL"
350,Stardew Valley,Level 10 Combat,"COMBAT_LEVEL,SKILL_LEVEL"
401,Carpenter Shop,Coop Blueprint,BUILDING_BLUEPRINT
402,Carpenter Shop,Big Coop Blueprint,BUILDING_BLUEPRINT
403,Carpenter Shop,Deluxe Coop Blueprint,BUILDING_BLUEPRINT
404,Carpenter Shop,Barn Blueprint,BUILDING_BLUEPRINT
405,Carpenter Shop,Big Barn Blueprint,BUILDING_BLUEPRINT
406,Carpenter Shop,Deluxe Barn Blueprint,BUILDING_BLUEPRINT
407,Carpenter Shop,Well Blueprint,BUILDING_BLUEPRINT
408,Carpenter Shop,Silo Blueprint,BUILDING_BLUEPRINT
409,Carpenter Shop,Mill Blueprint,BUILDING_BLUEPRINT
410,Carpenter Shop,Shed Blueprint,BUILDING_BLUEPRINT
411,Carpenter Shop,Big Shed Blueprint,BUILDING_BLUEPRINT
412,Carpenter Shop,Fish Pond Blueprint,BUILDING_BLUEPRINT
413,Carpenter Shop,Stable Blueprint,BUILDING_BLUEPRINT
414,Carpenter Shop,Slime Hutch Blueprint,BUILDING_BLUEPRINT
415,Carpenter Shop,Shipping Bin Blueprint,BUILDING_BLUEPRINT
416,Carpenter Shop,Kitchen Blueprint,BUILDING_BLUEPRINT
417,Carpenter Shop,Kids Room Blueprint,BUILDING_BLUEPRINT
418,Carpenter Shop,Cellar Blueprint,BUILDING_BLUEPRINT
501,Town,Introductions,"MANDATORY,QUEST"
502,Town,How To Win Friends,"MANDATORY,QUEST"
503,Farm,Getting Started,"MANDATORY,QUEST"
504,Farm,Raising Animals,"MANDATORY,QUEST"
505,Farm,Advancement,"MANDATORY,QUEST"
506,Museum,Archaeology,"MANDATORY,QUEST"
507,Wizard Tower,Meet The Wizard,"MANDATORY,QUEST"
508,Farm,Forging Ahead,"MANDATORY,QUEST"
509,Farm,Smelting,"MANDATORY,QUEST"
510,The Mines - Floor 5,Initiation,"MANDATORY,QUEST"
511,Forest,Robin's Lost Axe,"MANDATORY,QUEST"
512,Sam's House,Jodi's Request,"MANDATORY,QUEST"
513,Marnie's Ranch,"Mayor's ""Shorts""","MANDATORY,QUEST"
514,Tunnel Entrance,Blackberry Basket,"MANDATORY,QUEST"
515,Marnie's Ranch,Marnie's Request,"MANDATORY,QUEST"
516,Town,Pam Is Thirsty,"MANDATORY,QUEST"
517,Wizard Tower,A Dark Reagent,"MANDATORY,QUEST"
518,Marnie's Ranch,Cow's Delight,"MANDATORY,QUEST"
519,Skull Cavern Entrance,The Skull Key,"MANDATORY,QUEST"
520,Town,Crop Research,"MANDATORY,QUEST"
521,Town,Knee Therapy,"MANDATORY,QUEST"
522,Town,Robin's Request,"MANDATORY,QUEST"
523,Skull Cavern,Qi's Challenge,"MANDATORY,QUEST"
524,The Desert,The Mysterious Qi,"MANDATORY,QUEST"
525,Town,Carving Pumpkins,"MANDATORY,QUEST"
526,Town,A Winter Mystery,"MANDATORY,QUEST"
527,Secret Woods,Strange Note,"MANDATORY,QUEST"
528,Skull Cavern,Cryptic Note,"MANDATORY,QUEST"
529,Town,Fresh Fruit,"MANDATORY,QUEST"
530,Town,Aquatic Research,"MANDATORY,QUEST"
531,Town,A Soldier's Star,"MANDATORY,QUEST"
532,Town,Mayor's Need,"MANDATORY,QUEST"
533,Saloon,Wanted: Lobster,"MANDATORY,QUEST"
534,Town,Pam Needs Juice,"MANDATORY,QUEST"
535,Sam's House,Fish Casserole,"MANDATORY,QUEST"
536,Beach,Catch A Squid,"MANDATORY,QUEST"
537,Saloon,Fish Stew,"MANDATORY,QUEST"
538,Town,Pierre's Notice,"MANDATORY,QUEST"
539,Town,Clint's Attempt,"MANDATORY,QUEST"
540,Town,A Favor For Clint,"MANDATORY,QUEST"
541,Wizard Tower,Staff Of Power,"MANDATORY,QUEST"
542,Town,Granny's Gift,"MANDATORY,QUEST"
543,Saloon,Exotic Spirits,"MANDATORY,QUEST"
544,Town,Catch a Lingcod,"MANDATORY,QUEST"
601,JotPK World 1,JotPK: Boots 1,"ARCADE_MACHINE,JOTPK"
602,JotPK World 1,JotPK: Boots 2,"ARCADE_MACHINE,JOTPK"
603,JotPK World 1,JotPK: Gun 1,"ARCADE_MACHINE,JOTPK"
604,JotPK World 2,JotPK: Gun 2,"ARCADE_MACHINE,JOTPK"
605,JotPK World 2,JotPK: Gun 3,"ARCADE_MACHINE,JOTPK"
606,JotPK World 3,JotPK: Super Gun,"ARCADE_MACHINE,JOTPK"
607,JotPK World 1,JotPK: Ammo 1,"ARCADE_MACHINE,JOTPK"
608,JotPK World 2,JotPK: Ammo 2,"ARCADE_MACHINE,JOTPK"
609,JotPK World 3,JotPK: Ammo 3,"ARCADE_MACHINE,JOTPK"
610,JotPK World 1,JotPK: Cowboy 1,"ARCADE_MACHINE,JOTPK"
611,JotPK World 2,JotPK: Cowboy 2,"ARCADE_MACHINE,JOTPK"
612,Junimo Kart 1,Junimo Kart: Crumble Cavern,"ARCADE_MACHINE,JUNIMO_KART"
613,Junimo Kart 1,Junimo Kart: Slippery Slopes,"ARCADE_MACHINE,JUNIMO_KART"
614,Junimo Kart 2,Junimo Kart: Secret Level,"ARCADE_MACHINE,JUNIMO_KART"
615,Junimo Kart 2,Junimo Kart: The Gem Sea Giant,"ARCADE_MACHINE,JUNIMO_KART"
616,Junimo Kart 2,Junimo Kart: Slomp's Stomp,"ARCADE_MACHINE,JUNIMO_KART"
617,Junimo Kart 2,Junimo Kart: Ghastly Galleon,"ARCADE_MACHINE,JUNIMO_KART"
618,Junimo Kart 3,Junimo Kart: Glowshroom Grotto,"ARCADE_MACHINE,JUNIMO_KART"
619,Junimo Kart 3,Junimo Kart: Red Hot Rollercoaster,"ARCADE_MACHINE,JUNIMO_KART"
620,JotPK World 3,Journey of the Prairie King Victory,"ARCADE_MACHINE_VICTORY,JUNIMO_KART"
621,Junimo Kart 3,Junimo Kart: Sunset Speedway (Victory),"ARCADE_MACHINE_VICTORY,JUNIMO_KART"
701,Secret Woods,Old Master Cannoli,MANDATORY
702,Beach,Beach Bridge Repair,MANDATORY
703,The Desert,Galaxy Sword Shrine,MANDATORY
801,Town,Help Wanted: Gathering 1,HELP_WANTED
802,Town,Help Wanted: Gathering 2,HELP_WANTED
803,Town,Help Wanted: Gathering 3,HELP_WANTED
804,Town,Help Wanted: Gathering 4,HELP_WANTED
805,Town,Help Wanted: Gathering 5,HELP_WANTED
806,Town,Help Wanted: Gathering 6,HELP_WANTED
807,Town,Help Wanted: Gathering 7,HELP_WANTED
808,Town,Help Wanted: Gathering 8,HELP_WANTED
811,Town,Help Wanted: Slay Monsters 1,HELP_WANTED
812,Town,Help Wanted: Slay Monsters 2,HELP_WANTED
813,Town,Help Wanted: Slay Monsters 3,HELP_WANTED
814,Town,Help Wanted: Slay Monsters 4,HELP_WANTED
815,Town,Help Wanted: Slay Monsters 5,HELP_WANTED
816,Town,Help Wanted: Slay Monsters 6,HELP_WANTED
817,Town,Help Wanted: Slay Monsters 7,HELP_WANTED
818,Town,Help Wanted: Slay Monsters 8,HELP_WANTED
821,Town,Help Wanted: Fishing 1,HELP_WANTED
822,Town,Help Wanted: Fishing 2,HELP_WANTED
823,Town,Help Wanted: Fishing 3,HELP_WANTED
824,Town,Help Wanted: Fishing 4,HELP_WANTED
825,Town,Help Wanted: Fishing 5,HELP_WANTED
826,Town,Help Wanted: Fishing 6,HELP_WANTED
827,Town,Help Wanted: Fishing 7,HELP_WANTED
828,Town,Help Wanted: Fishing 8,HELP_WANTED
841,Town,Help Wanted: Item Delivery 1,HELP_WANTED
842,Town,Help Wanted: Item Delivery 2,HELP_WANTED
843,Town,Help Wanted: Item Delivery 3,HELP_WANTED
844,Town,Help Wanted: Item Delivery 4,HELP_WANTED
845,Town,Help Wanted: Item Delivery 5,HELP_WANTED
846,Town,Help Wanted: Item Delivery 6,HELP_WANTED
847,Town,Help Wanted: Item Delivery 7,HELP_WANTED
848,Town,Help Wanted: Item Delivery 8,HELP_WANTED
849,Town,Help Wanted: Item Delivery 9,HELP_WANTED
850,Town,Help Wanted: Item Delivery 10,HELP_WANTED
851,Town,Help Wanted: Item Delivery 11,HELP_WANTED
852,Town,Help Wanted: Item Delivery 12,HELP_WANTED
853,Town,Help Wanted: Item Delivery 13,HELP_WANTED
854,Town,Help Wanted: Item Delivery 14,HELP_WANTED
855,Town,Help Wanted: Item Delivery 15,HELP_WANTED
856,Town,Help Wanted: Item Delivery 16,HELP_WANTED
857,Town,Help Wanted: Item Delivery 17,HELP_WANTED
858,Town,Help Wanted: Item Delivery 18,HELP_WANTED
859,Town,Help Wanted: Item Delivery 19,HELP_WANTED
860,Town,Help Wanted: Item Delivery 20,HELP_WANTED
861,Town,Help Wanted: Item Delivery 21,HELP_WANTED
862,Town,Help Wanted: Item Delivery 22,HELP_WANTED
863,Town,Help Wanted: Item Delivery 23,HELP_WANTED
864,Town,Help Wanted: Item Delivery 24,HELP_WANTED
865,Town,Help Wanted: Item Delivery 25,HELP_WANTED
866,Town,Help Wanted: Item Delivery 26,HELP_WANTED
867,Town,Help Wanted: Item Delivery 27,HELP_WANTED
868,Town,Help Wanted: Item Delivery 28,HELP_WANTED
869,Town,Help Wanted: Item Delivery 29,HELP_WANTED
870,Town,Help Wanted: Item Delivery 30,HELP_WANTED
871,Town,Help Wanted: Item Delivery 31,HELP_WANTED
872,Town,Help Wanted: Item Delivery 32,HELP_WANTED
901,Forest,Traveling Merchant Sunday Item 1,"MANDATORY,TRAVELING_MERCHANT"
902,Forest,Traveling Merchant Sunday Item 2,"MANDATORY,TRAVELING_MERCHANT"
903,Forest,Traveling Merchant Sunday Item 3,"MANDATORY,TRAVELING_MERCHANT"
911,Forest,Traveling Merchant Monday Item 1,"MANDATORY,TRAVELING_MERCHANT"
912,Forest,Traveling Merchant Monday Item 2,"MANDATORY,TRAVELING_MERCHANT"
913,Forest,Traveling Merchant Monday Item 3,"MANDATORY,TRAVELING_MERCHANT"
921,Forest,Traveling Merchant Tuesday Item 1,"MANDATORY,TRAVELING_MERCHANT"
922,Forest,Traveling Merchant Tuesday Item 2,"MANDATORY,TRAVELING_MERCHANT"
923,Forest,Traveling Merchant Tuesday Item 3,"MANDATORY,TRAVELING_MERCHANT"
931,Forest,Traveling Merchant Wednesday Item 1,"MANDATORY,TRAVELING_MERCHANT"
932,Forest,Traveling Merchant Wednesday Item 2,"MANDATORY,TRAVELING_MERCHANT"
933,Forest,Traveling Merchant Wednesday Item 3,"MANDATORY,TRAVELING_MERCHANT"
941,Forest,Traveling Merchant Thursday Item 1,"MANDATORY,TRAVELING_MERCHANT"
942,Forest,Traveling Merchant Thursday Item 2,"MANDATORY,TRAVELING_MERCHANT"
943,Forest,Traveling Merchant Thursday Item 3,"MANDATORY,TRAVELING_MERCHANT"
951,Forest,Traveling Merchant Friday Item 1,"MANDATORY,TRAVELING_MERCHANT"
952,Forest,Traveling Merchant Friday Item 2,"MANDATORY,TRAVELING_MERCHANT"
953,Forest,Traveling Merchant Friday Item 3,"MANDATORY,TRAVELING_MERCHANT"
961,Forest,Traveling Merchant Saturday Item 1,"MANDATORY,TRAVELING_MERCHANT"
962,Forest,Traveling Merchant Saturday Item 2,"MANDATORY,TRAVELING_MERCHANT"
963,Forest,Traveling Merchant Saturday Item 3,"MANDATORY,TRAVELING_MERCHANT"
1001,Mountain,Fishsanity: Carp,FISHSANITY
1002,Beach,Fishsanity: Herring,FISHSANITY
1003,Forest,Fishsanity: Smallmouth Bass,FISHSANITY
1004,Beach,Fishsanity: Anchovy,FISHSANITY
1005,Beach,Fishsanity: Sardine,FISHSANITY
1006,Forest,Fishsanity: Sunfish,FISHSANITY
1007,Forest,Fishsanity: Perch,FISHSANITY
1008,Forest,Fishsanity: Chub,FISHSANITY
1009,Forest,Fishsanity: Bream,FISHSANITY
1010,Beach,Fishsanity: Red Snapper,FISHSANITY
1011,Beach,Fishsanity: Sea Cucumber,FISHSANITY
1012,Forest,Fishsanity: Rainbow Trout,FISHSANITY
1013,Forest,Fishsanity: Walleye,FISHSANITY
1014,Forest,Fishsanity: Shad,FISHSANITY
1015,Mountain,Fishsanity: Bullhead,FISHSANITY
1016,Mountain,Fishsanity: Largemouth Bass,FISHSANITY
1017,Forest,Fishsanity: Salmon,FISHSANITY
1018,The Mines - Floor 20,Fishsanity: Ghostfish,FISHSANITY
1019,Beach,Fishsanity: Tilapia,FISHSANITY
1020,Secret Woods,Fishsanity: Woodskip,FISHSANITY
1021,Beach,Fishsanity: Flounder,FISHSANITY
1022,Beach,Fishsanity: Halibut,FISHSANITY
1023,Ginger Island,Fishsanity: Lionfish,FISHSANITY
1024,Mutant Bug Lair,Fishsanity: Slimejack,FISHSANITY
1025,Forest,Fishsanity: Midnight Carp,FISHSANITY
1026,Beach,Fishsanity: Red Mullet,FISHSANITY
1027,Forest,Fishsanity: Pike,FISHSANITY
1028,Forest,Fishsanity: Tiger Trout,FISHSANITY
1029,Ginger Island,Fishsanity: Blue Discus,FISHSANITY
1030,Beach,Fishsanity: Albacore,FISHSANITY
1031,The Desert,Fishsanity: Sandfish,FISHSANITY
1032,The Mines - Floor 20,Fishsanity: Stonefish,FISHSANITY
1033,Beach,Fishsanity: Tuna,FISHSANITY
1034,Beach,Fishsanity: Eel,FISHSANITY
1035,Forest,Fishsanity: Catfish,FISHSANITY
1036,Beach,Fishsanity: Squid,FISHSANITY
1037,Mountain,Fishsanity: Sturgeon,FISHSANITY
1038,Forest,Fishsanity: Dorado,FISHSANITY
1039,Beach,Fishsanity: Pufferfish,FISHSANITY
1040,Witch's Swamp,Fishsanity: Void Salmon,FISHSANITY
1041,Beach,Fishsanity: Super Cucumber,FISHSANITY
1042,Ginger Island,Fishsanity: Stingray,FISHSANITY
1043,The Mines - Floor 60,Fishsanity: Ice Pip,FISHSANITY
1044,Forest,Fishsanity: Lingcod,FISHSANITY
1045,The Desert,Fishsanity: Scorpion Carp,FISHSANITY
1046,The Mines - Floor 100,Fishsanity: Lava Eel,FISHSANITY
1047,Beach,Fishsanity: Octopus,FISHSANITY
1048,Beach,Fishsanity: Midnight Squid,FISHSANITY
1049,Beach,Fishsanity: Spook Fish,FISHSANITY
1050,Beach,Fishsanity: Blobfish,FISHSANITY
1051,Beach,Fishsanity: Crimsonfish,FISHSANITY
1052,Town,Fishsanity: Angler,FISHSANITY
1053,Mountain,Fishsanity: Legend,FISHSANITY
1054,Forest,Fishsanity: Glacierfish,FISHSANITY
1055,Sewers,Fishsanity: Mutant Carp,FISHSANITY
1056,Town,Fishsanity: Crayfish,FISHSANITY
1057,Town,Fishsanity: Snail,FISHSANITY
1058,Town,Fishsanity: Periwinkle,FISHSANITY
1059,Beach,Fishsanity: Lobster,FISHSANITY
1060,Beach,Fishsanity: Clam,FISHSANITY
1061,Beach,Fishsanity: Crab,FISHSANITY
1062,Beach,Fishsanity: Cockle,FISHSANITY
1063,Beach,Fishsanity: Mussel,FISHSANITY
1064,Beach,Fishsanity: Shrimp,FISHSANITY
1065,Beach,Fishsanity: Oyster,FISHSANITY
1 id region name tags
2 1 Crafts Room Spring Foraging Bundle BUNDLE,COMMUNITY_CENTER_BUNDLE,CRAFTS_ROOM_BUNDLE,MANDATORY
3 2 Crafts Room Summer Foraging Bundle BUNDLE,COMMUNITY_CENTER_BUNDLE,CRAFTS_ROOM_BUNDLE,MANDATORY
4 3 Crafts Room Fall Foraging Bundle BUNDLE,COMMUNITY_CENTER_BUNDLE,CRAFTS_ROOM_BUNDLE,MANDATORY
5 4 Crafts Room Winter Foraging Bundle BUNDLE,COMMUNITY_CENTER_BUNDLE,CRAFTS_ROOM_BUNDLE,MANDATORY
6 5 Crafts Room Construction Bundle BUNDLE,COMMUNITY_CENTER_BUNDLE,CRAFTS_ROOM_BUNDLE,MANDATORY
7 6 Crafts Room Exotic Foraging Bundle BUNDLE,COMMUNITY_CENTER_BUNDLE,CRAFTS_ROOM_BUNDLE,MANDATORY
8 7 Pantry Spring Crops Bundle BUNDLE,COMMUNITY_CENTER_BUNDLE,MANDATORY,PANTRY_BUNDLE
9 8 Pantry Summer Crops Bundle BUNDLE,COMMUNITY_CENTER_BUNDLE,MANDATORY,PANTRY_BUNDLE
10 9 Pantry Fall Crops Bundle BUNDLE,COMMUNITY_CENTER_BUNDLE,MANDATORY,PANTRY_BUNDLE
11 10 Pantry Quality Crops Bundle BUNDLE,COMMUNITY_CENTER_BUNDLE,MANDATORY,PANTRY_BUNDLE
12 11 Pantry Animal Bundle BUNDLE,COMMUNITY_CENTER_BUNDLE,MANDATORY,PANTRY_BUNDLE
13 12 Pantry Artisan Bundle BUNDLE,COMMUNITY_CENTER_BUNDLE,MANDATORY,PANTRY_BUNDLE
14 13 Fish Tank River Fish Bundle BUNDLE,COMMUNITY_CENTER_BUNDLE,FISH_TANK_BUNDLE,MANDATORY
15 14 Fish Tank Lake Fish Bundle BUNDLE,COMMUNITY_CENTER_BUNDLE,FISH_TANK_BUNDLE,MANDATORY
16 15 Fish Tank Ocean Fish Bundle BUNDLE,COMMUNITY_CENTER_BUNDLE,FISH_TANK_BUNDLE,MANDATORY
17 16 Fish Tank Night Fishing Bundle BUNDLE,COMMUNITY_CENTER_BUNDLE,FISH_TANK_BUNDLE,MANDATORY
18 17 Fish Tank Crab Pot Bundle BUNDLE,COMMUNITY_CENTER_BUNDLE,FISH_TANK_BUNDLE,MANDATORY
19 18 Fish Tank Specialty Fish Bundle BUNDLE,COMMUNITY_CENTER_BUNDLE,FISH_TANK_BUNDLE,MANDATORY
20 19 Boiler Room Blacksmith's Bundle BOILER_ROOM_BUNDLE,BUNDLE,COMMUNITY_CENTER_BUNDLE,MANDATORY
21 20 Boiler Room Geologist's Bundle BOILER_ROOM_BUNDLE,BUNDLE,COMMUNITY_CENTER_BUNDLE,MANDATORY
22 21 Boiler Room Adventurer's Bundle BOILER_ROOM_BUNDLE,BUNDLE,COMMUNITY_CENTER_BUNDLE,MANDATORY
23 22 Bulletin Board Chef's Bundle BULLETIN_BOARD_BUNDLE,BUNDLE,COMMUNITY_CENTER_BUNDLE,MANDATORY
24 23 Bulletin Board Dye Bundle BULLETIN_BOARD_BUNDLE,BUNDLE,COMMUNITY_CENTER_BUNDLE,MANDATORY
25 24 Bulletin Board Field Research Bundle BULLETIN_BOARD_BUNDLE,BUNDLE,COMMUNITY_CENTER_BUNDLE,MANDATORY
26 25 Bulletin Board Fodder Bundle BULLETIN_BOARD_BUNDLE,BUNDLE,COMMUNITY_CENTER_BUNDLE,MANDATORY
27 26 Bulletin Board Enchanter's Bundle BULLETIN_BOARD_BUNDLE,BUNDLE,COMMUNITY_CENTER_BUNDLE,MANDATORY
28 27 Vault 2,500g Bundle BUNDLE,COMMUNITY_CENTER_BUNDLE,MANDATORY,VAULT_BUNDLE
29 28 Vault 5,000g Bundle BUNDLE,COMMUNITY_CENTER_BUNDLE,MANDATORY,VAULT_BUNDLE
30 29 Vault 10,000g Bundle BUNDLE,COMMUNITY_CENTER_BUNDLE,MANDATORY,VAULT_BUNDLE
31 30 Vault 25,000g Bundle BUNDLE,COMMUNITY_CENTER_BUNDLE,MANDATORY,VAULT_BUNDLE
32 31 Abandoned JojaMart The Missing Bundle BUNDLE
33 32 Crafts Room Complete Crafts Room COMMUNITY_CENTER_ROOM,MANDATORY
34 33 Pantry Complete Pantry COMMUNITY_CENTER_ROOM,MANDATORY
35 34 Fish Tank Complete Fish Tank COMMUNITY_CENTER_ROOM,MANDATORY
36 35 Boiler Room Complete Boiler Room COMMUNITY_CENTER_ROOM,MANDATORY
37 36 Bulletin Board Complete Bulletin Board COMMUNITY_CENTER_ROOM,MANDATORY
38 37 Vault Complete Vault COMMUNITY_CENTER_ROOM,MANDATORY
39 101 Pierre's General Store Large Pack BACKPACK
40 102 Pierre's General Store Deluxe Pack BACKPACK
41 103 Clint's Blacksmith Copper Hoe Upgrade HOE_UPGRADE,TOOL_UPGRADE
42 104 Clint's Blacksmith Iron Hoe Upgrade HOE_UPGRADE,TOOL_UPGRADE
43 105 Clint's Blacksmith Gold Hoe Upgrade HOE_UPGRADE,TOOL_UPGRADE
44 106 Clint's Blacksmith Iridium Hoe Upgrade HOE_UPGRADE,TOOL_UPGRADE
45 107 Clint's Blacksmith Copper Pickaxe Upgrade PICKAXE_UPGRADE,TOOL_UPGRADE
46 108 Clint's Blacksmith Iron Pickaxe Upgrade PICKAXE_UPGRADE,TOOL_UPGRADE
47 109 Clint's Blacksmith Gold Pickaxe Upgrade PICKAXE_UPGRADE,TOOL_UPGRADE
48 110 Clint's Blacksmith Iridium Pickaxe Upgrade PICKAXE_UPGRADE,TOOL_UPGRADE
49 111 Clint's Blacksmith Copper Axe Upgrade AXE_UPGRADE,TOOL_UPGRADE
50 112 Clint's Blacksmith Iron Axe Upgrade AXE_UPGRADE,TOOL_UPGRADE
51 113 Clint's Blacksmith Gold Axe Upgrade AXE_UPGRADE,TOOL_UPGRADE
52 114 Clint's Blacksmith Iridium Axe Upgrade AXE_UPGRADE,TOOL_UPGRADE
53 115 Clint's Blacksmith Copper Watering Can Upgrade TOOL_UPGRADE,WATERING_CAN_UPGRADE
54 116 Clint's Blacksmith Iron Watering Can Upgrade TOOL_UPGRADE,WATERING_CAN_UPGRADE
55 117 Clint's Blacksmith Gold Watering Can Upgrade TOOL_UPGRADE,WATERING_CAN_UPGRADE
56 118 Clint's Blacksmith Iridium Watering Can Upgrade TOOL_UPGRADE,WATERING_CAN_UPGRADE
57 119 Clint's Blacksmith Copper Trash Can Upgrade TOOL_UPGRADE,TRASH_CAN_UPGRADE
58 120 Clint's Blacksmith Iron Trash Can Upgrade TOOL_UPGRADE,TRASH_CAN_UPGRADE
59 121 Clint's Blacksmith Gold Trash Can Upgrade TOOL_UPGRADE,TRASH_CAN_UPGRADE
60 122 Clint's Blacksmith Iridium Trash Can Upgrade TOOL_UPGRADE,TRASH_CAN_UPGRADE
61 123 Willy's Fish Shop Purchase Training Rod FISHING_ROD_UPGRADE,TOOL_UPGRADE
62 124 Stardew Valley Bamboo Pole Cutscene FISHING_ROD_UPGRADE,TOOL_UPGRADE
63 125 Willy's Fish Shop Purchase Fiberglass Rod FISHING_ROD_UPGRADE,TOOL_UPGRADE
64 126 Willy's Fish Shop Purchase Iridium Rod FISHING_ROD_UPGRADE,TOOL_UPGRADE
65 201 The Mines - Floor 10 The Mines Floor 10 Treasure MANDATORY,THE_MINES_TREASURE
66 202 The Mines - Floor 20 The Mines Floor 20 Treasure MANDATORY,THE_MINES_TREASURE
67 203 The Mines - Floor 40 The Mines Floor 40 Treasure MANDATORY,THE_MINES_TREASURE
68 204 The Mines - Floor 50 The Mines Floor 50 Treasure MANDATORY,THE_MINES_TREASURE
69 205 The Mines - Floor 60 The Mines Floor 60 Treasure MANDATORY,THE_MINES_TREASURE
70 206 The Mines - Floor 70 The Mines Floor 70 Treasure MANDATORY,THE_MINES_TREASURE
71 207 The Mines - Floor 80 The Mines Floor 80 Treasure MANDATORY,THE_MINES_TREASURE
72 208 The Mines - Floor 90 The Mines Floor 90 Treasure MANDATORY,THE_MINES_TREASURE
73 209 The Mines - Floor 100 The Mines Floor 100 Treasure MANDATORY,THE_MINES_TREASURE
74 210 The Mines - Floor 110 The Mines Floor 110 Treasure MANDATORY,THE_MINES_TREASURE
75 211 The Mines - Floor 120 The Mines Floor 120 Treasure MANDATORY,THE_MINES_TREASURE
76 212 Quarry Mine Grim Reaper statue MANDATORY
77 213 The Mines The Mines Entrance Cutscene MANDATORY
78 214 The Mines - Floor 5 Floor 5 Elevator THE_MINES_ELEVATOR
79 215 The Mines - Floor 10 Floor 10 Elevator THE_MINES_ELEVATOR
80 216 The Mines - Floor 15 Floor 15 Elevator THE_MINES_ELEVATOR
81 217 The Mines - Floor 20 Floor 20 Elevator THE_MINES_ELEVATOR
82 218 The Mines - Floor 25 Floor 25 Elevator THE_MINES_ELEVATOR
83 219 The Mines - Floor 30 Floor 30 Elevator THE_MINES_ELEVATOR
84 220 The Mines - Floor 35 Floor 35 Elevator THE_MINES_ELEVATOR
85 221 The Mines - Floor 40 Floor 40 Elevator THE_MINES_ELEVATOR
86 222 The Mines - Floor 45 Floor 45 Elevator THE_MINES_ELEVATOR
87 223 The Mines - Floor 50 Floor 50 Elevator THE_MINES_ELEVATOR
88 224 The Mines - Floor 55 Floor 55 Elevator THE_MINES_ELEVATOR
89 225 The Mines - Floor 60 Floor 60 Elevator THE_MINES_ELEVATOR
90 226 The Mines - Floor 65 Floor 65 Elevator THE_MINES_ELEVATOR
91 227 The Mines - Floor 70 Floor 70 Elevator THE_MINES_ELEVATOR
92 228 The Mines - Floor 75 Floor 75 Elevator THE_MINES_ELEVATOR
93 229 The Mines - Floor 80 Floor 80 Elevator THE_MINES_ELEVATOR
94 230 The Mines - Floor 85 Floor 85 Elevator THE_MINES_ELEVATOR
95 231 The Mines - Floor 90 Floor 90 Elevator THE_MINES_ELEVATOR
96 232 The Mines - Floor 95 Floor 95 Elevator THE_MINES_ELEVATOR
97 233 The Mines - Floor 100 Floor 100 Elevator THE_MINES_ELEVATOR
98 234 The Mines - Floor 105 Floor 105 Elevator THE_MINES_ELEVATOR
99 235 The Mines - Floor 110 Floor 110 Elevator THE_MINES_ELEVATOR
100 236 The Mines - Floor 115 Floor 115 Elevator THE_MINES_ELEVATOR
101 237 The Mines - Floor 120 Floor 120 Elevator THE_MINES_ELEVATOR
102 301 Stardew Valley Level 1 Farming FARMING_LEVEL,SKILL_LEVEL
103 302 Stardew Valley Level 2 Farming FARMING_LEVEL,SKILL_LEVEL
104 303 Stardew Valley Level 3 Farming FARMING_LEVEL,SKILL_LEVEL
105 304 Stardew Valley Level 4 Farming FARMING_LEVEL,SKILL_LEVEL
106 305 Stardew Valley Level 5 Farming FARMING_LEVEL,SKILL_LEVEL
107 306 Stardew Valley Level 6 Farming FARMING_LEVEL,SKILL_LEVEL
108 307 Stardew Valley Level 7 Farming FARMING_LEVEL,SKILL_LEVEL
109 308 Stardew Valley Level 8 Farming FARMING_LEVEL,SKILL_LEVEL
110 309 Stardew Valley Level 9 Farming FARMING_LEVEL,SKILL_LEVEL
111 310 Stardew Valley Level 10 Farming FARMING_LEVEL,SKILL_LEVEL
112 311 Stardew Valley Level 1 Fishing FISHING_LEVEL,SKILL_LEVEL
113 312 Stardew Valley Level 2 Fishing FISHING_LEVEL,SKILL_LEVEL
114 313 Stardew Valley Level 3 Fishing FISHING_LEVEL,SKILL_LEVEL
115 314 Stardew Valley Level 4 Fishing FISHING_LEVEL,SKILL_LEVEL
116 315 Stardew Valley Level 5 Fishing FISHING_LEVEL,SKILL_LEVEL
117 316 Stardew Valley Level 6 Fishing FISHING_LEVEL,SKILL_LEVEL
118 317 Stardew Valley Level 7 Fishing FISHING_LEVEL,SKILL_LEVEL
119 318 Stardew Valley Level 8 Fishing FISHING_LEVEL,SKILL_LEVEL
120 319 Stardew Valley Level 9 Fishing FISHING_LEVEL,SKILL_LEVEL
121 320 Stardew Valley Level 10 Fishing FISHING_LEVEL,SKILL_LEVEL
122 321 Stardew Valley Level 1 Foraging FORAGING_LEVEL,SKILL_LEVEL
123 322 Stardew Valley Level 2 Foraging FORAGING_LEVEL,SKILL_LEVEL
124 323 Stardew Valley Level 3 Foraging FORAGING_LEVEL,SKILL_LEVEL
125 324 Stardew Valley Level 4 Foraging FORAGING_LEVEL,SKILL_LEVEL
126 325 Stardew Valley Level 5 Foraging FORAGING_LEVEL,SKILL_LEVEL
127 326 Stardew Valley Level 6 Foraging FORAGING_LEVEL,SKILL_LEVEL
128 327 Stardew Valley Level 7 Foraging FORAGING_LEVEL,SKILL_LEVEL
129 328 Stardew Valley Level 8 Foraging FORAGING_LEVEL,SKILL_LEVEL
130 329 Stardew Valley Level 9 Foraging FORAGING_LEVEL,SKILL_LEVEL
131 330 Stardew Valley Level 10 Foraging FORAGING_LEVEL,SKILL_LEVEL
132 331 Stardew Valley Level 1 Mining MINING_LEVEL,SKILL_LEVEL
133 332 Stardew Valley Level 2 Mining MINING_LEVEL,SKILL_LEVEL
134 333 Stardew Valley Level 3 Mining MINING_LEVEL,SKILL_LEVEL
135 334 Stardew Valley Level 4 Mining MINING_LEVEL,SKILL_LEVEL
136 335 Stardew Valley Level 5 Mining MINING_LEVEL,SKILL_LEVEL
137 336 Stardew Valley Level 6 Mining MINING_LEVEL,SKILL_LEVEL
138 337 Stardew Valley Level 7 Mining MINING_LEVEL,SKILL_LEVEL
139 338 Stardew Valley Level 8 Mining MINING_LEVEL,SKILL_LEVEL
140 339 Stardew Valley Level 9 Mining MINING_LEVEL,SKILL_LEVEL
141 340 Stardew Valley Level 10 Mining MINING_LEVEL,SKILL_LEVEL
142 341 Stardew Valley Level 1 Combat COMBAT_LEVEL,SKILL_LEVEL
143 342 Stardew Valley Level 2 Combat COMBAT_LEVEL,SKILL_LEVEL
144 343 Stardew Valley Level 3 Combat COMBAT_LEVEL,SKILL_LEVEL
145 344 Stardew Valley Level 4 Combat COMBAT_LEVEL,SKILL_LEVEL
146 345 Stardew Valley Level 5 Combat COMBAT_LEVEL,SKILL_LEVEL
147 346 Stardew Valley Level 6 Combat COMBAT_LEVEL,SKILL_LEVEL
148 347 Stardew Valley Level 7 Combat COMBAT_LEVEL,SKILL_LEVEL
149 348 Stardew Valley Level 8 Combat COMBAT_LEVEL,SKILL_LEVEL
150 349 Stardew Valley Level 9 Combat COMBAT_LEVEL,SKILL_LEVEL
151 350 Stardew Valley Level 10 Combat COMBAT_LEVEL,SKILL_LEVEL
152 401 Carpenter Shop Coop Blueprint BUILDING_BLUEPRINT
153 402 Carpenter Shop Big Coop Blueprint BUILDING_BLUEPRINT
154 403 Carpenter Shop Deluxe Coop Blueprint BUILDING_BLUEPRINT
155 404 Carpenter Shop Barn Blueprint BUILDING_BLUEPRINT
156 405 Carpenter Shop Big Barn Blueprint BUILDING_BLUEPRINT
157 406 Carpenter Shop Deluxe Barn Blueprint BUILDING_BLUEPRINT
158 407 Carpenter Shop Well Blueprint BUILDING_BLUEPRINT
159 408 Carpenter Shop Silo Blueprint BUILDING_BLUEPRINT
160 409 Carpenter Shop Mill Blueprint BUILDING_BLUEPRINT
161 410 Carpenter Shop Shed Blueprint BUILDING_BLUEPRINT
162 411 Carpenter Shop Big Shed Blueprint BUILDING_BLUEPRINT
163 412 Carpenter Shop Fish Pond Blueprint BUILDING_BLUEPRINT
164 413 Carpenter Shop Stable Blueprint BUILDING_BLUEPRINT
165 414 Carpenter Shop Slime Hutch Blueprint BUILDING_BLUEPRINT
166 415 Carpenter Shop Shipping Bin Blueprint BUILDING_BLUEPRINT
167 416 Carpenter Shop Kitchen Blueprint BUILDING_BLUEPRINT
168 417 Carpenter Shop Kids Room Blueprint BUILDING_BLUEPRINT
169 418 Carpenter Shop Cellar Blueprint BUILDING_BLUEPRINT
170 501 Town Introductions MANDATORY,QUEST
171 502 Town How To Win Friends MANDATORY,QUEST
172 503 Farm Getting Started MANDATORY,QUEST
173 504 Farm Raising Animals MANDATORY,QUEST
174 505 Farm Advancement MANDATORY,QUEST
175 506 Museum Archaeology MANDATORY,QUEST
176 507 Wizard Tower Meet The Wizard MANDATORY,QUEST
177 508 Farm Forging Ahead MANDATORY,QUEST
178 509 Farm Smelting MANDATORY,QUEST
179 510 The Mines - Floor 5 Initiation MANDATORY,QUEST
180 511 Forest Robin's Lost Axe MANDATORY,QUEST
181 512 Sam's House Jodi's Request MANDATORY,QUEST
182 513 Marnie's Ranch Mayor's "Shorts" MANDATORY,QUEST
183 514 Tunnel Entrance Blackberry Basket MANDATORY,QUEST
184 515 Marnie's Ranch Marnie's Request MANDATORY,QUEST
185 516 Town Pam Is Thirsty MANDATORY,QUEST
186 517 Wizard Tower A Dark Reagent MANDATORY,QUEST
187 518 Marnie's Ranch Cow's Delight MANDATORY,QUEST
188 519 Skull Cavern Entrance The Skull Key MANDATORY,QUEST
189 520 Town Crop Research MANDATORY,QUEST
190 521 Town Knee Therapy MANDATORY,QUEST
191 522 Town Robin's Request MANDATORY,QUEST
192 523 Skull Cavern Qi's Challenge MANDATORY,QUEST
193 524 The Desert The Mysterious Qi MANDATORY,QUEST
194 525 Town Carving Pumpkins MANDATORY,QUEST
195 526 Town A Winter Mystery MANDATORY,QUEST
196 527 Secret Woods Strange Note MANDATORY,QUEST
197 528 Skull Cavern Cryptic Note MANDATORY,QUEST
198 529 Town Fresh Fruit MANDATORY,QUEST
199 530 Town Aquatic Research MANDATORY,QUEST
200 531 Town A Soldier's Star MANDATORY,QUEST
201 532 Town Mayor's Need MANDATORY,QUEST
202 533 Saloon Wanted: Lobster MANDATORY,QUEST
203 534 Town Pam Needs Juice MANDATORY,QUEST
204 535 Sam's House Fish Casserole MANDATORY,QUEST
205 536 Beach Catch A Squid MANDATORY,QUEST
206 537 Saloon Fish Stew MANDATORY,QUEST
207 538 Town Pierre's Notice MANDATORY,QUEST
208 539 Town Clint's Attempt MANDATORY,QUEST
209 540 Town A Favor For Clint MANDATORY,QUEST
210 541 Wizard Tower Staff Of Power MANDATORY,QUEST
211 542 Town Granny's Gift MANDATORY,QUEST
212 543 Saloon Exotic Spirits MANDATORY,QUEST
213 544 Town Catch a Lingcod MANDATORY,QUEST
214 601 JotPK World 1 JotPK: Boots 1 ARCADE_MACHINE,JOTPK
215 602 JotPK World 1 JotPK: Boots 2 ARCADE_MACHINE,JOTPK
216 603 JotPK World 1 JotPK: Gun 1 ARCADE_MACHINE,JOTPK
217 604 JotPK World 2 JotPK: Gun 2 ARCADE_MACHINE,JOTPK
218 605 JotPK World 2 JotPK: Gun 3 ARCADE_MACHINE,JOTPK
219 606 JotPK World 3 JotPK: Super Gun ARCADE_MACHINE,JOTPK
220 607 JotPK World 1 JotPK: Ammo 1 ARCADE_MACHINE,JOTPK
221 608 JotPK World 2 JotPK: Ammo 2 ARCADE_MACHINE,JOTPK
222 609 JotPK World 3 JotPK: Ammo 3 ARCADE_MACHINE,JOTPK
223 610 JotPK World 1 JotPK: Cowboy 1 ARCADE_MACHINE,JOTPK
224 611 JotPK World 2 JotPK: Cowboy 2 ARCADE_MACHINE,JOTPK
225 612 Junimo Kart 1 Junimo Kart: Crumble Cavern ARCADE_MACHINE,JUNIMO_KART
226 613 Junimo Kart 1 Junimo Kart: Slippery Slopes ARCADE_MACHINE,JUNIMO_KART
227 614 Junimo Kart 2 Junimo Kart: Secret Level ARCADE_MACHINE,JUNIMO_KART
228 615 Junimo Kart 2 Junimo Kart: The Gem Sea Giant ARCADE_MACHINE,JUNIMO_KART
229 616 Junimo Kart 2 Junimo Kart: Slomp's Stomp ARCADE_MACHINE,JUNIMO_KART
230 617 Junimo Kart 2 Junimo Kart: Ghastly Galleon ARCADE_MACHINE,JUNIMO_KART
231 618 Junimo Kart 3 Junimo Kart: Glowshroom Grotto ARCADE_MACHINE,JUNIMO_KART
232 619 Junimo Kart 3 Junimo Kart: Red Hot Rollercoaster ARCADE_MACHINE,JUNIMO_KART
233 620 JotPK World 3 Journey of the Prairie King Victory ARCADE_MACHINE_VICTORY,JUNIMO_KART
234 621 Junimo Kart 3 Junimo Kart: Sunset Speedway (Victory) ARCADE_MACHINE_VICTORY,JUNIMO_KART
235 701 Secret Woods Old Master Cannoli MANDATORY
236 702 Beach Beach Bridge Repair MANDATORY
237 703 The Desert Galaxy Sword Shrine MANDATORY
238 801 Town Help Wanted: Gathering 1 HELP_WANTED
239 802 Town Help Wanted: Gathering 2 HELP_WANTED
240 803 Town Help Wanted: Gathering 3 HELP_WANTED
241 804 Town Help Wanted: Gathering 4 HELP_WANTED
242 805 Town Help Wanted: Gathering 5 HELP_WANTED
243 806 Town Help Wanted: Gathering 6 HELP_WANTED
244 807 Town Help Wanted: Gathering 7 HELP_WANTED
245 808 Town Help Wanted: Gathering 8 HELP_WANTED
246 811 Town Help Wanted: Slay Monsters 1 HELP_WANTED
247 812 Town Help Wanted: Slay Monsters 2 HELP_WANTED
248 813 Town Help Wanted: Slay Monsters 3 HELP_WANTED
249 814 Town Help Wanted: Slay Monsters 4 HELP_WANTED
250 815 Town Help Wanted: Slay Monsters 5 HELP_WANTED
251 816 Town Help Wanted: Slay Monsters 6 HELP_WANTED
252 817 Town Help Wanted: Slay Monsters 7 HELP_WANTED
253 818 Town Help Wanted: Slay Monsters 8 HELP_WANTED
254 821 Town Help Wanted: Fishing 1 HELP_WANTED
255 822 Town Help Wanted: Fishing 2 HELP_WANTED
256 823 Town Help Wanted: Fishing 3 HELP_WANTED
257 824 Town Help Wanted: Fishing 4 HELP_WANTED
258 825 Town Help Wanted: Fishing 5 HELP_WANTED
259 826 Town Help Wanted: Fishing 6 HELP_WANTED
260 827 Town Help Wanted: Fishing 7 HELP_WANTED
261 828 Town Help Wanted: Fishing 8 HELP_WANTED
262 841 Town Help Wanted: Item Delivery 1 HELP_WANTED
263 842 Town Help Wanted: Item Delivery 2 HELP_WANTED
264 843 Town Help Wanted: Item Delivery 3 HELP_WANTED
265 844 Town Help Wanted: Item Delivery 4 HELP_WANTED
266 845 Town Help Wanted: Item Delivery 5 HELP_WANTED
267 846 Town Help Wanted: Item Delivery 6 HELP_WANTED
268 847 Town Help Wanted: Item Delivery 7 HELP_WANTED
269 848 Town Help Wanted: Item Delivery 8 HELP_WANTED
270 849 Town Help Wanted: Item Delivery 9 HELP_WANTED
271 850 Town Help Wanted: Item Delivery 10 HELP_WANTED
272 851 Town Help Wanted: Item Delivery 11 HELP_WANTED
273 852 Town Help Wanted: Item Delivery 12 HELP_WANTED
274 853 Town Help Wanted: Item Delivery 13 HELP_WANTED
275 854 Town Help Wanted: Item Delivery 14 HELP_WANTED
276 855 Town Help Wanted: Item Delivery 15 HELP_WANTED
277 856 Town Help Wanted: Item Delivery 16 HELP_WANTED
278 857 Town Help Wanted: Item Delivery 17 HELP_WANTED
279 858 Town Help Wanted: Item Delivery 18 HELP_WANTED
280 859 Town Help Wanted: Item Delivery 19 HELP_WANTED
281 860 Town Help Wanted: Item Delivery 20 HELP_WANTED
282 861 Town Help Wanted: Item Delivery 21 HELP_WANTED
283 862 Town Help Wanted: Item Delivery 22 HELP_WANTED
284 863 Town Help Wanted: Item Delivery 23 HELP_WANTED
285 864 Town Help Wanted: Item Delivery 24 HELP_WANTED
286 865 Town Help Wanted: Item Delivery 25 HELP_WANTED
287 866 Town Help Wanted: Item Delivery 26 HELP_WANTED
288 867 Town Help Wanted: Item Delivery 27 HELP_WANTED
289 868 Town Help Wanted: Item Delivery 28 HELP_WANTED
290 869 Town Help Wanted: Item Delivery 29 HELP_WANTED
291 870 Town Help Wanted: Item Delivery 30 HELP_WANTED
292 871 Town Help Wanted: Item Delivery 31 HELP_WANTED
293 872 Town Help Wanted: Item Delivery 32 HELP_WANTED
294 901 Forest Traveling Merchant Sunday Item 1 MANDATORY,TRAVELING_MERCHANT
295 902 Forest Traveling Merchant Sunday Item 2 MANDATORY,TRAVELING_MERCHANT
296 903 Forest Traveling Merchant Sunday Item 3 MANDATORY,TRAVELING_MERCHANT
297 911 Forest Traveling Merchant Monday Item 1 MANDATORY,TRAVELING_MERCHANT
298 912 Forest Traveling Merchant Monday Item 2 MANDATORY,TRAVELING_MERCHANT
299 913 Forest Traveling Merchant Monday Item 3 MANDATORY,TRAVELING_MERCHANT
300 921 Forest Traveling Merchant Tuesday Item 1 MANDATORY,TRAVELING_MERCHANT
301 922 Forest Traveling Merchant Tuesday Item 2 MANDATORY,TRAVELING_MERCHANT
302 923 Forest Traveling Merchant Tuesday Item 3 MANDATORY,TRAVELING_MERCHANT
303 931 Forest Traveling Merchant Wednesday Item 1 MANDATORY,TRAVELING_MERCHANT
304 932 Forest Traveling Merchant Wednesday Item 2 MANDATORY,TRAVELING_MERCHANT
305 933 Forest Traveling Merchant Wednesday Item 3 MANDATORY,TRAVELING_MERCHANT
306 941 Forest Traveling Merchant Thursday Item 1 MANDATORY,TRAVELING_MERCHANT
307 942 Forest Traveling Merchant Thursday Item 2 MANDATORY,TRAVELING_MERCHANT
308 943 Forest Traveling Merchant Thursday Item 3 MANDATORY,TRAVELING_MERCHANT
309 951 Forest Traveling Merchant Friday Item 1 MANDATORY,TRAVELING_MERCHANT
310 952 Forest Traveling Merchant Friday Item 2 MANDATORY,TRAVELING_MERCHANT
311 953 Forest Traveling Merchant Friday Item 3 MANDATORY,TRAVELING_MERCHANT
312 961 Forest Traveling Merchant Saturday Item 1 MANDATORY,TRAVELING_MERCHANT
313 962 Forest Traveling Merchant Saturday Item 2 MANDATORY,TRAVELING_MERCHANT
314 963 Forest Traveling Merchant Saturday Item 3 MANDATORY,TRAVELING_MERCHANT
315 1001 Mountain Fishsanity: Carp FISHSANITY
316 1002 Beach Fishsanity: Herring FISHSANITY
317 1003 Forest Fishsanity: Smallmouth Bass FISHSANITY
318 1004 Beach Fishsanity: Anchovy FISHSANITY
319 1005 Beach Fishsanity: Sardine FISHSANITY
320 1006 Forest Fishsanity: Sunfish FISHSANITY
321 1007 Forest Fishsanity: Perch FISHSANITY
322 1008 Forest Fishsanity: Chub FISHSANITY
323 1009 Forest Fishsanity: Bream FISHSANITY
324 1010 Beach Fishsanity: Red Snapper FISHSANITY
325 1011 Beach Fishsanity: Sea Cucumber FISHSANITY
326 1012 Forest Fishsanity: Rainbow Trout FISHSANITY
327 1013 Forest Fishsanity: Walleye FISHSANITY
328 1014 Forest Fishsanity: Shad FISHSANITY
329 1015 Mountain Fishsanity: Bullhead FISHSANITY
330 1016 Mountain Fishsanity: Largemouth Bass FISHSANITY
331 1017 Forest Fishsanity: Salmon FISHSANITY
332 1018 The Mines - Floor 20 Fishsanity: Ghostfish FISHSANITY
333 1019 Beach Fishsanity: Tilapia FISHSANITY
334 1020 Secret Woods Fishsanity: Woodskip FISHSANITY
335 1021 Beach Fishsanity: Flounder FISHSANITY
336 1022 Beach Fishsanity: Halibut FISHSANITY
337 1023 Ginger Island Fishsanity: Lionfish FISHSANITY
338 1024 Mutant Bug Lair Fishsanity: Slimejack FISHSANITY
339 1025 Forest Fishsanity: Midnight Carp FISHSANITY
340 1026 Beach Fishsanity: Red Mullet FISHSANITY
341 1027 Forest Fishsanity: Pike FISHSANITY
342 1028 Forest Fishsanity: Tiger Trout FISHSANITY
343 1029 Ginger Island Fishsanity: Blue Discus FISHSANITY
344 1030 Beach Fishsanity: Albacore FISHSANITY
345 1031 The Desert Fishsanity: Sandfish FISHSANITY
346 1032 The Mines - Floor 20 Fishsanity: Stonefish FISHSANITY
347 1033 Beach Fishsanity: Tuna FISHSANITY
348 1034 Beach Fishsanity: Eel FISHSANITY
349 1035 Forest Fishsanity: Catfish FISHSANITY
350 1036 Beach Fishsanity: Squid FISHSANITY
351 1037 Mountain Fishsanity: Sturgeon FISHSANITY
352 1038 Forest Fishsanity: Dorado FISHSANITY
353 1039 Beach Fishsanity: Pufferfish FISHSANITY
354 1040 Witch's Swamp Fishsanity: Void Salmon FISHSANITY
355 1041 Beach Fishsanity: Super Cucumber FISHSANITY
356 1042 Ginger Island Fishsanity: Stingray FISHSANITY
357 1043 The Mines - Floor 60 Fishsanity: Ice Pip FISHSANITY
358 1044 Forest Fishsanity: Lingcod FISHSANITY
359 1045 The Desert Fishsanity: Scorpion Carp FISHSANITY
360 1046 The Mines - Floor 100 Fishsanity: Lava Eel FISHSANITY
361 1047 Beach Fishsanity: Octopus FISHSANITY
362 1048 Beach Fishsanity: Midnight Squid FISHSANITY
363 1049 Beach Fishsanity: Spook Fish FISHSANITY
364 1050 Beach Fishsanity: Blobfish FISHSANITY
365 1051 Beach Fishsanity: Crimsonfish FISHSANITY
366 1052 Town Fishsanity: Angler FISHSANITY
367 1053 Mountain Fishsanity: Legend FISHSANITY
368 1054 Forest Fishsanity: Glacierfish FISHSANITY
369 1055 Sewers Fishsanity: Mutant Carp FISHSANITY
370 1056 Town Fishsanity: Crayfish FISHSANITY
371 1057 Town Fishsanity: Snail FISHSANITY
372 1058 Town Fishsanity: Periwinkle FISHSANITY
373 1059 Beach Fishsanity: Lobster FISHSANITY
374 1060 Beach Fishsanity: Clam FISHSANITY
375 1061 Beach Fishsanity: Crab FISHSANITY
376 1062 Beach Fishsanity: Cockle FISHSANITY
377 1063 Beach Fishsanity: Mussel FISHSANITY
378 1064 Beach Fishsanity: Shrimp FISHSANITY
379 1065 Beach Fishsanity: Oyster FISHSANITY

View File

@@ -0,0 +1,39 @@
name,default_amount,scaling_factor,classification,groups
Money,1000,500,useful,BASE_RESOURCE
Stone,50,25,filler,BASE_RESOURCE
Wood,50,25,filler,BASE_RESOURCE
Hardwood,10,5,useful,BASE_RESOURCE
Fiber,30,15,filler,BASE_RESOURCE
Coal,10,5,filler,BASE_RESOURCE
Clay,10,5,filler,BASE_RESOURCE
Warp Totem: Beach,5,2,filler,WARP_TOTEM
Warp Totem: Desert,5,2,filler,WARP_TOTEM
Warp Totem: Farm,5,2,filler,WARP_TOTEM
Warp Totem: Island,5,2,filler,WARP_TOTEM
Warp Totem: Mountains,5,2,filler,WARP_TOTEM
Geode,12,6,filler,GEODE
Frozen Geode,8,4,filler,GEODE
Magma Geode,6,3,filler,GEODE
Omni Geode,4,2,useful,GEODE
Copper Ore,75,25,filler,ORE
Iron Ore,50,25,filler,ORE
Gold Ore,25,13,useful,ORE
Iridium Ore,10,5,useful,ORE
Quartz,10,5,filler,ORE
Basic Fertilizer,30,10,filler,FERTILIZER
Basic Retaining Soil,30,10,filler,FERTILIZER
Speed-Gro,30,10,filler,FERTILIZER
Quality Fertilizer,20,8,filler,FERTILIZER
Quality Retaining Soil,20,8,filler,FERTILIZER
Deluxe Speed-Gro,20,8,filler,FERTILIZER
Deluxe Fertilizer,10,4,useful,FERTILIZER
Deluxe Retaining Soil,10,4,useful,FERTILIZER
Hyper Speed-Gro,10,4,useful,FERTILIZER
Tree Fertilizer,10,4,filler,FERTILIZER
Spring Seeds,30,10,filler,SEED
Summer Seeds,30,10,filler,SEED
Fall Seeds,30,10,filler,SEED
Winter Seeds,30,10,filler,SEED
Mahogany Seed,5,2,filler,SEED
Bait,30,10,filler,FISHING_RESOURCE
Crab Pot,3,1,filler,FISHING_RESOURCE
1 name default_amount scaling_factor classification groups
2 Money 1000 500 useful BASE_RESOURCE
3 Stone 50 25 filler BASE_RESOURCE
4 Wood 50 25 filler BASE_RESOURCE
5 Hardwood 10 5 useful BASE_RESOURCE
6 Fiber 30 15 filler BASE_RESOURCE
7 Coal 10 5 filler BASE_RESOURCE
8 Clay 10 5 filler BASE_RESOURCE
9 Warp Totem: Beach 5 2 filler WARP_TOTEM
10 Warp Totem: Desert 5 2 filler WARP_TOTEM
11 Warp Totem: Farm 5 2 filler WARP_TOTEM
12 Warp Totem: Island 5 2 filler WARP_TOTEM
13 Warp Totem: Mountains 5 2 filler WARP_TOTEM
14 Geode 12 6 filler GEODE
15 Frozen Geode 8 4 filler GEODE
16 Magma Geode 6 3 filler GEODE
17 Omni Geode 4 2 useful GEODE
18 Copper Ore 75 25 filler ORE
19 Iron Ore 50 25 filler ORE
20 Gold Ore 25 13 useful ORE
21 Iridium Ore 10 5 useful ORE
22 Quartz 10 5 filler ORE
23 Basic Fertilizer 30 10 filler FERTILIZER
24 Basic Retaining Soil 30 10 filler FERTILIZER
25 Speed-Gro 30 10 filler FERTILIZER
26 Quality Fertilizer 20 8 filler FERTILIZER
27 Quality Retaining Soil 20 8 filler FERTILIZER
28 Deluxe Speed-Gro 20 8 filler FERTILIZER
29 Deluxe Fertilizer 10 4 useful FERTILIZER
30 Deluxe Retaining Soil 10 4 useful FERTILIZER
31 Hyper Speed-Gro 10 4 useful FERTILIZER
32 Tree Fertilizer 10 4 filler FERTILIZER
33 Spring Seeds 30 10 filler SEED
34 Summer Seeds 30 10 filler SEED
35 Fall Seeds 30 10 filler SEED
36 Winter Seeds 30 10 filler SEED
37 Mahogany Seed 5 2 filler SEED
38 Bait 30 10 filler FISHING_RESOURCE
39 Crab Pot 3 1 filler FISHING_RESOURCE

View File

@@ -0,0 +1,71 @@
# Stardew Valley
## Where is the settings page?
The [player settings page for this game](../player-settings) contains all the options you need to configure and export a
config file.
## What does randomization do to this game?
A vast number of optional objectives in stardew valley can be shuffled around the multiworld. Most of these are optional, and the player can customize their experience in their YAML file.
For these objectives, if they have a vanilla reward, this reward will instead be an item in the multiworld. For the remaining number of such objectives, there are a number of "Resource Pack" items, which are simply a stack of an item that may be useful to the player.
## What is the goal of Stardew Valley?
The player can choose from a number of goals, using their YAML settings.
- Complete the Community Center
- Succeed Grandpa's Evaluation with 4 lit candles
- Reach the bottom of the Pelican Town Mineshaft
- Complete the "Cryptic Note" quest, by meeting Mr Qi on floor 100 of the Skull Cavern
- Get the achievement "Master Angler", which requires catching every fish in the game
## What are location check in Stardew Valley?
Location checks in Stardew Valley always include:
- Community Center Bundles
- Mineshaft chest rewards
- Story Quests
- Traveling Merchant items
- Isolated objectives such as the beach bridge, Old Master Cannoli, Grim Reaper Statue, etc
There also are a number of location checks that are optional, and individual players choose to include them or not in their shuffling:
- Tools and Fishing Rod Upgrades
- Carpenter Buildings
- Backpack Upgrades
- Mine elevator levels
- Skill Levels
- Arcade Machines
- Help Wanted quests
- Fishsanity: Catching individual fish
## Which items can be in another player's world?
Every normal reward from the above locations can be in another player's world.
For the locations which do not include a normal reward, Resource Packs are instead added to the pool. These can contain ores, seeds, fertilizers, warp totems, etc.
There are a few extra items, which are added to the pool but do not have a matching location. These include
- Wizard Buildings
- Return Scepter
And lastly, some Archipelago-exclusive items exist in the pool, which are designed around game balance and QoL. These include:
- Arcade Machine buffs (Only if the arcade machines are randomized)
- Journey of the Prairie King has drop rate increases, extra lives, and equipment
- Junimo Kart has extra lives.
- Permanent Movement Speed Bonuses (customizable)
- Permanent Luck Bonuses (customizable)
- Traveling Merchant buffs
## When the player receives an item, what happens?
Since Pelican Town is a remote area, it takes one business day for every item to reach the player. If an item is received while online, it will appear in the player's mailbox the next morning, with a message from the sender telling them where it was found.
If an item is received while offline, it will be in the mailbox as soon as the player logs in.
Some items will be directly attached to the letter, while some others will instead be a world-wide unlock, and the letter only serves to tell the player about it.
In some cases, like receiving Carpenter and Wizard buildings, the player will still need to go ask Robin to construct the building that they have received, so they can choose its position. This construction will be completely free.
## Multiplayer
You cannot play an Archipelago Slot in multiplayer at the moment. There is no short-terms plans to support that feature.
You can, however, send Stardew Valley objects as gifts from one Stardew Player to another Stardew player, using in-game Joja Prime delivery, for a fee. This exclusive feature can be turned off if you don't want to send and receive gifts.

View File

@@ -0,0 +1,74 @@
# Stardew Valley Randomizer Setup Guide
## Required Software
- Stardew Valley on PC (Recommended: [Steam version](https://store.steampowered.com/app/413150/Stardew_Valley/))
- SMAPI ([Mod loader for Stardew Valley](https://smapi.io/))
- [StardewArchipelago Mod Release 2.x.x](https://github.com/agilbert1412/StardewArchipelago/releases)
- It is important to use a mod release of version 2.x.x to play seeds that have been generated here. Later releases can only be used with later releases of the world generator, that are not hosted on archipelago.gg yet.
## Optional Software
- Archipelago from the [Archipelago Releases Page](https://github.com/ArchipelagoMW/Archipelago/releases)
- (Only for the TextClient)
- Other Stardew Valley Mods [Nexus Mods](https://www.nexusmods.com/stardewvalley)
- It is **not** recommended to further mod Stardew Valley, altough it is possible to do so. Mod interactions can be unpredictable, and no support will be offered for related bugs.
- The more mods you have, and the bigger they are, the more likely things are to break.
## Configuring your YAML file
### What is a YAML file and why do I need one?
See the guide on setting up a basic YAML at the Archipelago setup
guide: [Basic Multiworld Setup Guide](/tutorial/Archipelago/setup/en)
### Where do I get a YAML file?
You can customize your settings by visiting the [Stardew Valley Player Settings Page](/games/Stardew Valley/player-settings)
## Joining a MultiWorld Game
### Installing the mod
- Install [SMAPI](https://smapi.io/) by following the instructions on their website
- Download and extract the [StardewArchipelago](https://github.com/agilbert1412/StardewArchipelago/releases) mod into your Stardew Valley "Mods" folder
- *OPTIONAL*: If you want to launch your game through Steam, add the following to your Stardew Valley launch options:
- "[PATH TO STARDEW VALLEY]\Stardew Valley\StardewModdingAPI.exe" %command%
- Otherwise just launch "StardewModdingAPI.exe" in your installation folder directly
- Stardew Valley should launch itself alongside a console which allows you to read mod information and interact with some of them.
### Connect to the MultiServer
Launch Stardew Valley with SMAPI. Once you have reached the Stardew Valley title screen, create a new farm.
On the new character creation page, you will see 3 new fields, used to link your new character to an archipelago multiworld
![image](https://i.imgur.com/b8KZy2F.png)
You can customize your farm and character as much as desired.
The Server text box needs to have both the address and the port, and your slotname is the name specified in your yaml
`archipelago.gg:38281`
`StardewPlayer`
The password is optional.
Your game will connect automatically to Archipelago, and reconnect automatically when loading the save, later.
You will never need to enter this information again for this character.
### Interacting with the MultiWorld from in-game
When you connect, you should see a message in the chat informing you of the `!!help` command. This command will list other Stardew-exclusive chat commands you can use.
Furthermore, you can use the in-game chat box to talk to other players in the multiworld, assuming they are using a game that supports chatting.
Lastly, you can also run Archipelago commands `!help` from the in game chat box, allowing you to request hints on certain items, or check missing locations.
It is important to note that the Stardew Valley chat is fairly limited in its capabilities. For example, it doesn't allow scrolling up to see history that has been pushed off screen. The SMAPI console running alonside your game will have the full history as well and may be better suited to read older messages.
For a better chat experience, you can also use the official Archipelago Text Client, altough it will not allow you to run Stardew-exclusive commands.
### Multiplayer
You cannot play an Archipelago Slot in multiplayer at the moment. There is no short-terms plans to support that feature.

View File

@@ -0,0 +1,127 @@
from typing import List, Tuple
from .game_item import FishItem
spring = ("Spring",)
summer = ("Summer",)
fall = ("Fall",)
winter = ("Winter",)
spring_summer = (*spring, *summer)
spring_fall = (*spring, *fall)
spring_winter = (*spring, *winter)
summer_fall = (*summer, *fall)
summer_winter = (*summer, *winter)
fall_winter = (*fall, *winter)
spring_summer_fall = (*spring, *summer, *fall)
spring_summer_winter = (*spring, *summer, *winter)
spring_fall_winter = (*spring, *fall, *winter)
all_seasons = (*spring, *summer, *fall, *winter)
town = ("Town",)
beach = ("Beach",)
mountain = ("Mountain",)
forest = ("Forest",)
secret_woods = ("Secret Woods",)
desert = ("The Desert",)
mines_20 = ("The Mines - Floor 20",)
mines_60 = ("The Mines - Floor 60",)
mines_100 = ("The Mines - Floor 100",)
sewers = ("Sewers",)
mutant_bug_lair = ("Mutant Bug Lair",)
witch_swamp = ("Witch's Swamp",)
ginger_island = ("Ginger Island",)
ginger_island_ocean = ginger_island
ginger_island_river = ginger_island
pirate_cove = ginger_island
night_market = beach
lakes = (*mountain, *secret_woods, *sewers)
ocean = beach
rivers = (*town, *forest)
rivers_secret_woods = (*rivers, *secret_woods)
forest_mountain = (*forest, *mountain)
rivers_mountain_lake = (*town, *forest, *mountain)
mines_20_60 = (*mines_20, *mines_60)
all_fish_items: List[FishItem] = []
def fish(name: str, item_id: int, locations: Tuple[str, ...], seasons: Tuple[str, ...], difficulty: int) -> FishItem:
fish_item = FishItem(name, item_id, locations, seasons, difficulty)
all_fish_items.append(fish_item)
return fish_item
carp = fish("Carp", 142, lakes, all_seasons, 15)
herring = fish("Herring", 147, ocean, spring_winter, 25)
smallmouth_bass = fish("Smallmouth Bass", 137, rivers, spring_fall, 28)
anchovy = fish("Anchovy", 129, ocean, spring_fall, 30)
sardine = fish("Sardine", 131, ocean, spring_fall_winter, 30)
sunfish = fish("Sunfish", 145, rivers, spring_summer, 30)
perch = fish("Perch", 141, rivers_mountain_lake, winter, 35)
chub = fish("Chub", 702, forest_mountain, all_seasons, 35)
bream = fish("Bream", 132, rivers, all_seasons, 35)
red_snapper = fish("Red Snapper", 150, ocean, summer_fall, 40)
sea_cucumber = fish("Sea Cucumber", 154, ocean, fall_winter, 40)
rainbow_trout = fish("Rainbow Trout", 138, rivers_mountain_lake, summer, 45)
walleye = fish("Walleye", 140, rivers_mountain_lake, fall, 45)
shad = fish("Shad", 706, rivers, spring_summer_fall, 45)
bullhead = fish("Bullhead", 700, mountain, all_seasons, 46)
largemouth_bass = fish("Largemouth Bass", 136, mountain, all_seasons, 50)
salmon = fish("Salmon", 139, rivers, fall, 50)
ghostfish = fish("Ghostfish", 156, mines_20_60, all_seasons, 50)
tilapia = fish("Tilapia", 701, ocean, summer_fall, 50)
woodskip = fish("Woodskip", 734, secret_woods, all_seasons, 50)
flounder = fish("Flounder", 267, ocean, spring_summer, 50)
halibut = fish("Halibut", 708, ocean, spring_summer_winter, 50)
lionfish = fish("Lionfish", 837, ginger_island_ocean, all_seasons, 50)
slimejack = fish("Slimejack", 796, mutant_bug_lair, all_seasons, 55)
midnight_carp = fish("Midnight Carp", 269, forest_mountain, fall_winter, 55)
red_mullet = fish("Red Mullet", 146, ocean, summer_winter, 55)
pike = fish("Pike", 144, rivers, summer_winter, 60)
tiger_trout = fish("Tiger Trout", 699, rivers, fall_winter, 60)
blue_discus = fish("Blue Discus", 838, ginger_island_river, all_seasons, 60)
albacore = fish("Albacore", 705, ocean, fall_winter, 60)
sandfish = fish("Sandfish", 164, desert, all_seasons, 65)
stonefish = fish("Stonefish", 158, mines_20, all_seasons, 65)
tuna = fish("Tuna", 130, ocean, summer_winter, 70)
eel = fish("Eel", 148, ocean, spring_fall, 70)
catfish = fish("Catfish", 143, rivers_secret_woods, spring_fall, 75)
squid = fish("Squid", 151, ocean, winter, 75)
sturgeon = fish("Sturgeon", 698, mountain, summer_winter, 78)
dorado = fish("Dorado", 704, forest, summer, 78)
pufferfish = fish("Pufferfish", 128, ocean, summer, 80)
void_salmon = fish("Void Salmon", 795, witch_swamp, all_seasons, 80)
super_cucumber = fish("Super Cucumber", 155, ocean, summer_fall, 80)
stingray = fish("Stingray", 836, pirate_cove, all_seasons, 80)
ice_pip = fish("Ice Pip", 161, mines_60, all_seasons, 85)
lingcod = fish("Lingcod", 707, rivers_mountain_lake, winter, 85)
scorpion_carp = fish("Scorpion Carp", 165, desert, all_seasons, 90)
lava_eel = fish("Lava Eel", 162, mines_100, all_seasons, 90)
octopus = fish("Octopus", 149, ocean, summer, 95)
midnight_squid = fish("Midnight Squid", 798, night_market, winter, 55)
spook_fish = fish("Spook Fish", 799, night_market, winter, 60)
blob_fish = fish("Blobfish", 800, night_market, winter, 75)
crimsonfish = fish("Crimsonfish", 159, ocean, summer, 95)
angler = fish("Angler", 160, town, fall, 85)
legend = fish("Legend", 163, mountain, spring, 110)
glacierfish = fish("Glacierfish", 775, forest, winter, 100)
mutant_carp = fish("Mutant Carp", 682, sewers, all_seasons, 80)
crayfish = fish("Crayfish", 716, rivers, all_seasons, -1)
snail = fish("Snail", 721, rivers, all_seasons, -1)
periwinkle = fish("Periwinkle", 722, rivers, all_seasons, -1)
lobster = fish("Lobster", 715, ocean, all_seasons, -1)
clam = fish("Clam", 372, ocean, all_seasons, -1)
crab = fish("Crab", 717, ocean, all_seasons, -1)
cockle = fish("Cockle", 718, ocean, all_seasons, -1)
mussel = fish("Mussel", 719, ocean, all_seasons, -1)
shrimp = fish("Shrimp", 720, ocean, all_seasons, -1)
oyster = fish("Oyster", 723, ocean, all_seasons, -1)
legendary_fish = [crimsonfish, angler, legend, glacierfish, mutant_carp]
special_fish = [*legendary_fish, blob_fish, lava_eel, octopus, scorpion_carp, ice_pip, super_cucumber, dorado]
all_fish_items_by_name = {fish.name: fish for fish in all_fish_items}
all_fish_items_by_id = {fish.item_id: fish for fish in all_fish_items}

View File

@@ -0,0 +1,26 @@
from dataclasses import dataclass
from typing import Tuple
@dataclass(frozen=True)
class GameItem:
name: str
item_id: int
def __repr__(self):
return f"{self.name} [{self.item_id}]"
def __lt__(self, other):
return self.name < other.name
@dataclass(frozen=True)
class FishItem(GameItem):
locations: Tuple[str]
seasons: Tuple[str]
difficulty: int
def __repr__(self):
return f"{self.name} [{self.item_id}] (Locations: {self.locations} |" \
f" Seasons: {self.seasons} |" \
f" Difficulty: {self.difficulty}) "

View File

@@ -0,0 +1,376 @@
import bisect
import csv
import enum
import itertools
import logging
import math
import typing
from collections import OrderedDict
from dataclasses import dataclass, field
from functools import cached_property
from pathlib import Path
from random import Random
from typing import Dict, List, Protocol, Union, Set, Optional, FrozenSet
from BaseClasses import Item, ItemClassification
from . import options, data
ITEM_CODE_OFFSET = 717000
logger = logging.getLogger(__name__)
world_folder = Path(__file__).parent
class Group(enum.Enum):
RESOURCE_PACK = enum.auto()
FRIENDSHIP_PACK = enum.auto()
COMMUNITY_REWARD = enum.auto()
TRASH = enum.auto()
MINES_FLOOR_10 = enum.auto()
MINES_FLOOR_20 = enum.auto()
MINES_FLOOR_50 = enum.auto()
MINES_FLOOR_60 = enum.auto()
MINES_FLOOR_80 = enum.auto()
MINES_FLOOR_90 = enum.auto()
MINES_FLOOR_110 = enum.auto()
FOOTWEAR = enum.auto()
HATS = enum.auto()
RING = enum.auto()
WEAPON = enum.auto()
PROGRESSIVE_TOOLS = enum.auto()
SKILL_LEVEL_UP = enum.auto()
ARCADE_MACHINE_BUFFS = enum.auto()
GALAXY_WEAPONS = enum.auto()
BASE_RESOURCE = enum.auto()
WARP_TOTEM = enum.auto()
GEODE = enum.auto()
ORE = enum.auto()
FERTILIZER = enum.auto()
SEED = enum.auto()
FISHING_RESOURCE = enum.auto()
@dataclass(frozen=True)
class ItemData:
code_without_offset: Optional[int]
name: str
classification: ItemClassification
groups: Set[Group] = field(default_factory=frozenset)
def __post_init__(self):
if not isinstance(self.groups, frozenset):
super().__setattr__("groups", frozenset(self.groups))
@property
def code(self):
return ITEM_CODE_OFFSET + self.code_without_offset if self.code_without_offset is not None else None
def has_any_group(self, *group: Group) -> bool:
groups = set(group)
return bool(groups.intersection(self.groups))
@dataclass(frozen=True)
class ResourcePackData:
name: str
default_amount: int = 1
scaling_factor: int = 1
classification: ItemClassification = ItemClassification.filler
groups: FrozenSet[Group] = frozenset()
def as_item_data(self, counter: itertools.count) -> [ItemData]:
return [ItemData(next(counter), self.create_item_name(quantity), self.classification,
{Group.RESOURCE_PACK} | self.groups)
for quantity in self.scale_quantity.values()]
def create_item_name(self, quantity: int) -> str:
return f"Resource Pack: {quantity} {self.name}"
@cached_property
def scale_quantity(self) -> typing.OrderedDict[int, int]:
"""Discrete scaling of the resource pack quantities.
100 is default, 200 is double, 50 is half (if the scaling_factor allows it).
"""
levels = math.ceil(self.default_amount / self.scaling_factor) * 2
first_level = self.default_amount % self.scaling_factor
if first_level == 0:
first_level = self.scaling_factor
quantities = sorted(set(range(first_level, self.scaling_factor * levels, self.scaling_factor))
| {self.default_amount * 2})
return OrderedDict({round(quantity / self.default_amount * 100): quantity
for quantity in quantities
if quantity <= self.default_amount * 2})
def calculate_quantity(self, multiplier: int) -> int:
scales = list(self.scale_quantity)
left_scale = bisect.bisect_left(scales, multiplier)
closest_scale = min([scales[left_scale], scales[left_scale - 1]],
key=lambda x: abs(multiplier - x))
return self.scale_quantity[closest_scale]
def create_name_from_multiplier(self, multiplier: int) -> str:
return self.create_item_name(self.calculate_quantity(multiplier))
class FriendshipPackData(ResourcePackData):
def create_item_name(self, quantity: int) -> str:
return f"Friendship Bonus ({quantity} <3)"
def as_item_data(self, counter: itertools.count) -> [ItemData]:
item_datas = super().as_item_data(counter)
return [ItemData(item.code_without_offset, item.name, item.classification, {Group.FRIENDSHIP_PACK})
for item in item_datas]
class StardewItemFactory(Protocol):
def __call__(self, name: Union[str, ItemData]) -> Item:
raise NotImplementedError
def load_item_csv():
try:
from importlib.resources import files
except ImportError:
from importlib_resources import files
items = []
with files(data).joinpath("items.csv").open() as file:
item_reader = csv.DictReader(file)
for item in item_reader:
id = int(item["id"]) if item["id"] else None
classification = ItemClassification[item["classification"]]
groups = {Group[group] for group in item["groups"].split(",") if group}
items.append(ItemData(id, item["name"], classification, groups))
return items
def load_resource_pack_csv() -> List[ResourcePackData]:
try:
from importlib.resources import files
except ImportError:
from importlib_resources import files
resource_packs = []
with files(data).joinpath("resource_packs.csv").open() as file:
resource_pack_reader = csv.DictReader(file)
for resource_pack in resource_pack_reader:
groups = frozenset(Group[group] for group in resource_pack["groups"].split(",") if group)
resource_packs.append(ResourcePackData(resource_pack["name"],
int(resource_pack["default_amount"]),
int(resource_pack["scaling_factor"]),
ItemClassification[resource_pack["classification"]],
groups))
return resource_packs
events = [
ItemData(None, "Victory", ItemClassification.progression),
ItemData(None, "Spring", ItemClassification.progression),
ItemData(None, "Summer", ItemClassification.progression),
ItemData(None, "Fall", ItemClassification.progression),
ItemData(None, "Winter", ItemClassification.progression),
ItemData(None, "Year Two", ItemClassification.progression),
]
all_items: List[ItemData] = load_item_csv() + events
item_table: Dict[str, ItemData] = {}
items_by_group: Dict[Group, List[ItemData]] = {}
def initialize_groups():
for item in all_items:
for group in item.groups:
item_group = items_by_group.get(group, list())
item_group.append(item)
items_by_group[group] = item_group
def initialize_item_table():
item_table.update({item.name: item for item in all_items})
friendship_pack = FriendshipPackData("Friendship Bonus", default_amount=2, classification=ItemClassification.useful)
all_resource_packs = load_resource_pack_csv()
initialize_item_table()
initialize_groups()
def create_items(item_factory: StardewItemFactory, locations_count: int, world_options: options.StardewOptions,
random: Random) \
-> List[Item]:
items = create_unique_items(item_factory, world_options, random)
assert len(items) <= locations_count, \
"There should be at least as many locations as there are mandatory items"
logger.debug(f"Created {len(items)} unique items")
resource_pack_items = fill_with_resource_packs(item_factory, world_options, random, locations_count - len(items))
items += resource_pack_items
logger.debug(f"Created {len(resource_pack_items)} resource packs")
return items
def create_backpack_items(item_factory: StardewItemFactory, world_options: options.StardewOptions, items: List[Item]):
if (world_options[options.BackpackProgression] == options.BackpackProgression.option_progressive or
world_options[options.BackpackProgression] == options.BackpackProgression.option_early_progressive):
items.extend(item_factory(item) for item in ["Progressive Backpack"] * 2)
def create_mine_rewards(item_factory: StardewItemFactory, items: List[Item], random: Random):
items.append(item_factory("Rusty Sword"))
items.append(item_factory(random.choice(items_by_group[Group.MINES_FLOOR_10])))
items.append(item_factory(random.choice(items_by_group[Group.MINES_FLOOR_20])))
items.append(item_factory("Slingshot"))
items.append(item_factory(random.choice(items_by_group[Group.MINES_FLOOR_50])))
items.append(item_factory(random.choice(items_by_group[Group.MINES_FLOOR_60])))
items.append(item_factory("Master Slingshot"))
items.append(item_factory(random.choice(items_by_group[Group.MINES_FLOOR_80])))
items.append(item_factory(random.choice(items_by_group[Group.MINES_FLOOR_90])))
items.append(item_factory(random.choice(items_by_group[Group.MINES_FLOOR_110])))
items.append(item_factory("Skull Key"))
def create_mine_elevators(item_factory: StardewItemFactory, world_options: options.StardewOptions, items: List[Item]):
if (world_options[options.TheMinesElevatorsProgression] ==
options.TheMinesElevatorsProgression.option_progressive or
world_options[options.TheMinesElevatorsProgression] ==
options.TheMinesElevatorsProgression.option_progressive_from_previous_floor):
items.extend([item_factory(item) for item in ["Progressive Mine Elevator"] * 24])
def create_tools(item_factory: StardewItemFactory, world_options: options.StardewOptions, items: List[Item]):
if world_options[options.ToolProgression] == options.ToolProgression.option_progressive:
items.extend(item_factory(item) for item in items_by_group[Group.PROGRESSIVE_TOOLS] * 4)
items.append(item_factory("Golden Scythe"))
def create_skills(item_factory: StardewItemFactory, world_options: options.StardewOptions, items: List[Item]):
if world_options[options.SkillProgression] == options.SkillProgression.option_progressive:
items.extend([item_factory(item) for item in items_by_group[Group.SKILL_LEVEL_UP] * 10])
def create_wizard_buildings(item_factory: StardewItemFactory, items: List[Item]):
items.append(item_factory("Earth Obelisk"))
items.append(item_factory("Water Obelisk"))
items.append(item_factory("Desert Obelisk"))
items.append(item_factory("Island Obelisk"))
items.append(item_factory("Junimo Hut"))
items.append(item_factory("Gold Clock"))
def create_carpenter_buildings(item_factory: StardewItemFactory, world_options: options.StardewOptions,
items: List[Item]):
if world_options[options.BuildingProgression] in {options.BuildingProgression.option_progressive,
options.BuildingProgression.option_progressive_early_shipping_bin}:
items.append(item_factory("Progressive Coop"))
items.append(item_factory("Progressive Coop"))
items.append(item_factory("Progressive Coop"))
items.append(item_factory("Progressive Barn"))
items.append(item_factory("Progressive Barn"))
items.append(item_factory("Progressive Barn"))
items.append(item_factory("Well"))
items.append(item_factory("Silo"))
items.append(item_factory("Mill"))
items.append(item_factory("Progressive Shed"))
items.append(item_factory("Progressive Shed"))
items.append(item_factory("Fish Pond"))
items.append(item_factory("Stable"))
items.append(item_factory("Slime Hutch"))
items.append(item_factory("Shipping Bin"))
items.append(item_factory("Progressive House"))
items.append(item_factory("Progressive House"))
items.append(item_factory("Progressive House"))
def create_special_quest_rewards(item_factory: StardewItemFactory, items: List[Item]):
items.append(item_factory("Adventurer's Guild"))
items.append(item_factory("Club Card"))
items.append(item_factory("Magnifying Glass"))
items.append(item_factory("Bear's Knowledge"))
items.append(item_factory("Iridium Snake Milk"))
def create_stardrops(item_factory: StardewItemFactory, items: List[Item]):
items.append(item_factory("Stardrop")) # The Mines level 100
items.append(item_factory("Stardrop")) # Old Master Cannoli
def create_arcade_machine_items(item_factory: StardewItemFactory, world_options: options.StardewOptions,
items: List[Item]):
if world_options[options.ArcadeMachineLocations] == options.ArcadeMachineLocations.option_full_shuffling:
items.append(item_factory("JotPK: Progressive Boots"))
items.append(item_factory("JotPK: Progressive Boots"))
items.append(item_factory("JotPK: Progressive Gun"))
items.append(item_factory("JotPK: Progressive Gun"))
items.append(item_factory("JotPK: Progressive Gun"))
items.append(item_factory("JotPK: Progressive Gun"))
items.append(item_factory("JotPK: Progressive Ammo"))
items.append(item_factory("JotPK: Progressive Ammo"))
items.append(item_factory("JotPK: Progressive Ammo"))
items.append(item_factory("JotPK: Extra Life"))
items.append(item_factory("JotPK: Extra Life"))
items.append(item_factory("JotPK: Increased Drop Rate"))
items.extend(item_factory(item) for item in ["Junimo Kart: Extra Life"] * 8)
def create_player_buffs(item_factory: StardewItemFactory, world_options: options.StardewOptions, items: List[Item]):
number_of_buffs: int = world_options[options.NumberOfPlayerBuffs]
items.extend(item_factory(item) for item in ["Movement Speed Bonus"] * number_of_buffs)
items.extend(item_factory(item) for item in ["Luck Bonus"] * number_of_buffs)
def create_traveling_merchant_items(item_factory: StardewItemFactory, items: List[Item]):
items.append(item_factory("Traveling Merchant: Sunday"))
items.append(item_factory("Traveling Merchant: Monday"))
items.append(item_factory("Traveling Merchant: Tuesday"))
items.append(item_factory("Traveling Merchant: Wednesday"))
items.append(item_factory("Traveling Merchant: Thursday"))
items.append(item_factory("Traveling Merchant: Friday"))
items.append(item_factory("Traveling Merchant: Saturday"))
items.extend(item_factory(item) for item in ["Traveling Merchant Stock Size"] * 6)
items.extend(item_factory(item) for item in ["Traveling Merchant Discount"] * 8)
def create_unique_items(item_factory: StardewItemFactory, world_options: options.StardewOptions, random: Random) -> \
List[Item]:
items = []
items.extend(item_factory(item) for item in items_by_group[Group.COMMUNITY_REWARD])
create_backpack_items(item_factory, world_options, items)
create_mine_rewards(item_factory, items, random)
create_mine_elevators(item_factory, world_options, items)
create_tools(item_factory, world_options, items)
create_skills(item_factory, world_options, items)
create_wizard_buildings(item_factory, items)
create_carpenter_buildings(item_factory, world_options, items)
items.append(item_factory("Beach Bridge"))
create_special_quest_rewards(item_factory, items)
create_stardrops(item_factory, items)
create_arcade_machine_items(item_factory, world_options, items)
items.append(item_factory(random.choice(items_by_group[Group.GALAXY_WEAPONS])))
items.append(
item_factory(friendship_pack.create_name_from_multiplier(world_options[options.ResourcePackMultiplier])))
create_player_buffs(item_factory, world_options, items)
create_traveling_merchant_items(item_factory, items)
items.append(item_factory("Return Scepter"))
return items
def fill_with_resource_packs(item_factory: StardewItemFactory, world_options: options.StardewOptions, random: Random,
required_resource_pack: int) -> List[Item]:
resource_pack_multiplier = world_options[options.ResourcePackMultiplier]
if resource_pack_multiplier == 0:
return [item_factory(cola) for cola in ["Joja Cola"] * required_resource_pack]
items = []
for i in range(required_resource_pack):
resource_pack = random.choice(all_resource_packs)
items.append(item_factory(resource_pack.create_name_from_multiplier(resource_pack_multiplier)))
return items

View File

@@ -0,0 +1,175 @@
import csv
import enum
from dataclasses import dataclass
from random import Random
from typing import Optional, Dict, Protocol, List, FrozenSet
from . import options, data
from .fish_data import legendary_fish, special_fish, all_fish_items
LOCATION_CODE_OFFSET = 717000
class LocationTags(enum.Enum):
MANDATORY = enum.auto()
BUNDLE = enum.auto()
COMMUNITY_CENTER_BUNDLE = enum.auto()
CRAFTS_ROOM_BUNDLE = enum.auto()
PANTRY_BUNDLE = enum.auto()
FISH_TANK_BUNDLE = enum.auto()
BOILER_ROOM_BUNDLE = enum.auto()
BULLETIN_BOARD_BUNDLE = enum.auto()
VAULT_BUNDLE = enum.auto()
COMMUNITY_CENTER_ROOM = enum.auto()
BACKPACK = enum.auto()
TOOL_UPGRADE = enum.auto()
HOE_UPGRADE = enum.auto()
PICKAXE_UPGRADE = enum.auto()
AXE_UPGRADE = enum.auto()
WATERING_CAN_UPGRADE = enum.auto()
TRASH_CAN_UPGRADE = enum.auto()
FISHING_ROD_UPGRADE = enum.auto()
THE_MINES_TREASURE = enum.auto()
THE_MINES_ELEVATOR = enum.auto()
SKILL_LEVEL = enum.auto()
FARMING_LEVEL = enum.auto()
FISHING_LEVEL = enum.auto()
FORAGING_LEVEL = enum.auto()
COMBAT_LEVEL = enum.auto()
MINING_LEVEL = enum.auto()
BUILDING_BLUEPRINT = enum.auto()
QUEST = enum.auto()
ARCADE_MACHINE = enum.auto()
ARCADE_MACHINE_VICTORY = enum.auto()
JOTPK = enum.auto()
JUNIMO_KART = enum.auto()
HELP_WANTED = enum.auto()
TRAVELING_MERCHANT = enum.auto()
FISHSANITY = enum.auto()
@dataclass(frozen=True)
class LocationData:
code_without_offset: Optional[int]
region: str
name: str
tags: FrozenSet[LocationTags] = frozenset()
@property
def code(self) -> Optional[int]:
return LOCATION_CODE_OFFSET + self.code_without_offset if self.code_without_offset is not None else None
class StardewLocationCollector(Protocol):
def __call__(self, name: str, code: Optional[int], region: str) -> None:
raise NotImplementedError
def load_location_csv() -> List[LocationData]:
try:
from importlib.resources import files
except ImportError:
from importlib_resources import files
with files(data).joinpath("locations.csv").open() as file:
reader = csv.DictReader(file)
return [LocationData(int(location["id"]) if location["id"] else None,
location["region"],
location["name"],
frozenset(LocationTags[group]
for group in location["tags"].split(",")
if group))
for location in reader]
events_locations = [
LocationData(None, "Stardew Valley", "Succeed Grandpa's Evaluation"),
LocationData(None, "Community Center", "Complete Community Center"),
LocationData(None, "The Mines - Floor 120", "Reach the Bottom of The Mines"),
LocationData(None, "Skull Cavern", "Complete Quest Cryptic Note"),
LocationData(None, "Stardew Valley", "Catch Every Fish"),
LocationData(None, "Stardew Valley", "Summer"),
LocationData(None, "Stardew Valley", "Fall"),
LocationData(None, "Stardew Valley", "Winter"),
LocationData(None, "Stardew Valley", "Year Two"),
]
all_locations = load_location_csv() + events_locations
location_table: Dict[str, LocationData] = {location.name: location for location in all_locations}
locations_by_tag: Dict[LocationTags, List[LocationData]] = {}
def initialize_groups():
for location in all_locations:
for tag in location.tags:
location_group = locations_by_tag.get(tag, list())
location_group.append(location)
locations_by_tag[tag] = location_group
initialize_groups()
def extend_help_wanted_quests(randomized_locations: List[LocationData], desired_number_of_quests: int):
for i in range(0, desired_number_of_quests):
batch = i // 7
index_this_batch = i % 7
if index_this_batch < 4:
randomized_locations.append(
location_table[f"Help Wanted: Item Delivery {(batch * 4) + index_this_batch + 1}"])
elif index_this_batch == 4:
randomized_locations.append(location_table[f"Help Wanted: Fishing {batch + 1}"])
elif index_this_batch == 5:
randomized_locations.append(location_table[f"Help Wanted: Slay Monsters {batch + 1}"])
elif index_this_batch == 6:
randomized_locations.append(location_table[f"Help Wanted: Gathering {batch + 1}"])
def extend_fishsanity_locations(randomized_locations: List[LocationData], fishsanity: int, random: Random):
prefix = "Fishsanity: "
if fishsanity == options.Fishsanity.option_none:
return
elif fishsanity == options.Fishsanity.option_legendaries:
randomized_locations.extend(location_table[f"{prefix}{legendary.name}"] for legendary in legendary_fish)
elif fishsanity == options.Fishsanity.option_special:
randomized_locations.extend(location_table[f"{prefix}{special.name}"] for special in special_fish)
elif fishsanity == options.Fishsanity.option_random_selection:
randomized_locations.extend(location_table[f"{prefix}{fish.name}"]
for fish in all_fish_items if random.random() < 0.4)
elif fishsanity == options.Fishsanity.option_all:
randomized_locations.extend(location_table[f"{prefix}{fish.name}"] for fish in all_fish_items)
def create_locations(location_collector: StardewLocationCollector,
world_options: options.StardewOptions,
random: Random):
randomized_locations = []
randomized_locations.extend(locations_by_tag[LocationTags.MANDATORY])
if not world_options[options.BackpackProgression] == options.BackpackProgression.option_vanilla:
randomized_locations.extend(locations_by_tag[LocationTags.BACKPACK])
if not world_options[options.ToolProgression] == options.ToolProgression.option_vanilla:
randomized_locations.extend(locations_by_tag[LocationTags.TOOL_UPGRADE])
if not world_options[options.TheMinesElevatorsProgression] == options.TheMinesElevatorsProgression.option_vanilla:
randomized_locations.extend(locations_by_tag[LocationTags.THE_MINES_ELEVATOR])
if not world_options[options.SkillProgression] == options.SkillProgression.option_vanilla:
randomized_locations.extend(locations_by_tag[LocationTags.SKILL_LEVEL])
if not world_options[options.BuildingProgression] == options.BuildingProgression.option_vanilla:
randomized_locations.extend(locations_by_tag[LocationTags.BUILDING_BLUEPRINT])
if not world_options[options.ArcadeMachineLocations] == options.ArcadeMachineLocations.option_disabled:
randomized_locations.extend(locations_by_tag[LocationTags.ARCADE_MACHINE_VICTORY])
if world_options[options.ArcadeMachineLocations] == options.ArcadeMachineLocations.option_full_shuffling:
randomized_locations.extend(locations_by_tag[LocationTags.ARCADE_MACHINE])
extend_help_wanted_quests(randomized_locations, world_options[options.HelpWantedLocations])
extend_fishsanity_locations(randomized_locations, world_options[options.Fishsanity], random)
for location_data in randomized_locations:
location_collector(location_data.name, location_data.code, location_data.region)

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,409 @@
from dataclasses import dataclass
from typing import Dict, Union, Protocol, runtime_checkable
from Options import Option, Range, DeathLink, SpecialRange, Toggle, Choice
@runtime_checkable
class StardewOption(Protocol):
internal_name: str
@dataclass
class StardewOptions:
options: Dict[str, Union[bool, int]]
def __getitem__(self, item: Union[str, StardewOption]) -> Union[bool, int]:
if isinstance(item, StardewOption):
item = item.internal_name
return self.options.get(item, None)
class Goal(Choice):
"""What's your goal with this play-through?
With Community Center, the world will be completed once you complete the Community Center.
With Grandpa's Evaluation, the world will be completed once 4 candles are lit around Grandpa's Shrine.
With Bottom of the Mines, the world will be completed once you reach level 120 in the local mineshaft.
With Cryptic Note, the world will be completed once you complete the quest "Cryptic Note" where Mr Qi asks you to reach floor 100 in the Skull Cavern
With Master Angler, the world will be completed once you have caught every fish in the game. Pairs well with Fishsanity"""
internal_name = "goal"
display_name = "Goal"
option_community_center = 0
option_grandpa_evaluation = 1
option_bottom_of_the_mines = 2
option_cryptic_note = 3
option_master_angler = 4
@classmethod
def get_option_name(cls, value) -> str:
if value == cls.option_grandpa_evaluation:
return "Grandpa's Evaluation"
return super().get_option_name(value)
class StartingMoney(SpecialRange):
"""Amount of gold when arriving at the farm.
Set to -1 or unlimited for infinite money in this playthrough"""
internal_name = "starting_money"
display_name = "Starting Gold"
range_start = -1
range_end = 50000
default = 5000
special_range_names = {
"unlimited": -1,
"vanilla": 500,
"extra": 2000,
"rich": 5000,
"very rich": 20000,
"filthy rich": 50000,
}
class ResourcePackMultiplier(SpecialRange):
"""How many items will be in the resource pack. A lower setting mean fewer resources in each pack.
A higher setting means more resources in each pack. Easy (200) doubles the default quantity.
This also include Friendship bonuses that replace the one from the Bulletin Board."""
internal_name = "resource_pack_multiplier"
default = 100
range_start = 0
range_end = 200
# step = 25
display_name = "Resource Pack Multiplier"
special_range_names = {
"resource packs disabled": 0,
"half packs": 50,
"normal packs": 100,
"double packs": 200,
}
class BundleRandomization(Choice):
"""What items are needed for the community center bundles?
With Vanilla, you get the standard bundles from the game
With Thematic, every bundle will require random items within their original category
With Shuffled, every bundle will require random items without logic"""
internal_name = "bundle_randomization"
display_name = "Bundle Randomization"
default = 1
option_vanilla = 0
option_thematic = 1
option_shuffled = 2
class BundlePrice(Choice):
"""How many items are needed for the community center bundles?
With Very Cheap, every bundle will require two items fewer than usual
With Cheap, every bundle will require 1 item fewer than usual
With Normal, every bundle will require the vanilla number of items
With Expensive, every bundle will require 1 extra item"""
internal_name = "bundle_price"
display_name = "Bundle Price"
default = 2
option_very_cheap = 0
option_cheap = 1
option_normal = 2
option_expensive = 3
class EntranceRandomization(Choice):
"""Should area entrances be randomized?
With Disabled, no entrance randomization is done
With Pelican Town, only buildings in the main town area are randomized with each other
With Non Progression, only buildings that are always available are randomized with each other
"""
# With Buildings, All buildings in the world are randomized with each other
# With Everything, All buildings and areas are randomized with each other
# With Chaos, same as everything, but the buildings are shuffled again every in-game day. You can't learn it!
internal_name = "entrance_randomization"
display_name = "Entrance Randomization"
default = 0
option_disabled = 0
option_pelican_town = 1
option_non_progression = 2
# option_buildings = 3
# option_everything = 4
# option_chaos = 4
class BackpackProgression(Choice):
"""How is the backpack progression handled?
With Vanilla, you can buy them at Pierre's.
With Progressive, you will randomly find Progressive Backpack to upgrade.
With Early Progressive, you can expect you first Backpack before the second season, and the third before the forth
season.
"""
internal_name = "backpack_progression"
display_name = "Backpack Progression"
default = 2
option_vanilla = 0
option_progressive = 1
option_early_progressive = 2
class ToolProgression(Choice):
"""How is the tool progression handled?
With Vanilla, Clint will upgrade your tools with ore.
With Progressive, you will randomly find Progressive Tool to upgrade.
With World Checks, the tools of different quality will be found in the world."""
internal_name = "tool_progression"
display_name = "Tool Progression"
default = 1
option_vanilla = 0
option_progressive = 1
class TheMinesElevatorsProgression(Choice):
"""How is The Mines' Elevator progression handled?
With Vanilla, you will unlock a new elevator floor every 5 floor in the mine.
With Progressive, you will randomly find Progressive Mine Elevator to go deeper. Location are sent for reaching
every level multiple of 5.
With Progressive from previous floor, you will randomly find Progressive Mine Elevator to go deeper. Location are
sent for taking the ladder or stair to every level multiple of 5, taking the elevator does not count."""
internal_name = "elevator_progression"
display_name = "Elevator Progression"
default = 2
option_vanilla = 0
option_progressive = 1
option_progressive_from_previous_floor = 2
class SkillProgression(Choice):
"""How is the skill progression handled?
With Vanilla, you will level up and get the normal reward at each level.
With Progressive, the xp will be counted internally, locations will be sent when you gain a virtual level. Your real
levels will be scattered around the world."""
internal_name = "skill_progression"
display_name = "Skill Progression"
default = 1
option_vanilla = 0
option_progressive = 1
class BuildingProgression(Choice):
"""How is the building progression handled?
With Vanilla, you will buy each building and upgrade one at the time.
With Progressive, you will receive the buildings and will be able to build the first one of each building for free,
once it is received. If you want more of the same building, it will cost the vanilla price.
This option INCLUDES the shipping bin as a building you need to receive.
With Progressive early shipping bin, you can expect to receive the shipping bin before the end of the first season.
"""
internal_name = "building_progression"
display_name = "Building Progression"
default = 2
option_vanilla = 0
option_progressive = 1
option_progressive_early_shipping_bin = 2
class ArcadeMachineLocations(Choice):
"""How are the Arcade Machines handled?
With Vanilla, the arcade machines are not included in the Archipelago shuffling.
With Victories, each Arcade Machine will contain one check on victory
With Victories Easy, the arcade machines are both made considerably easier to be more accessible for the average
player.
With Full Shuffling, the arcade machines will contain multiple checks each, and different buffs that make the game
easier are in the item pool. Junimo Kart has one check at the end of each level.
Journey of the Prairie King has one check after each boss, plus one check for each vendor equipment.
"""
internal_name = "arcade_machine_locations"
display_name = "Arcade Machine Locations"
default = 3
option_disabled = 0
option_victories = 1
option_victories_easy = 2
option_full_shuffling = 3
class HelpWantedLocations(SpecialRange):
"""How many "Help Wanted" quests need to be completed as ArchipelagoLocations
Out of every 7 quests, 4 will be item deliveries, and then 1 of each for: Fishing, Gathering and Slaying Monsters.
Choosing a multiple of 7 is recommended."""
internal_name = "help_wanted_locations"
default = 7
range_start = 0
range_end = 56
# step = 7
display_name = "Number of Help Wanted locations"
special_range_names = {
"none": 0,
"minimum": 7,
"normal": 14,
"lots": 28,
"maximum": 56,
}
class Fishsanity(Choice):
"""Locations for catching fish?
With None, there are no locations for catching fish
With Legendaries, each of the 5 legendary fish are locations that contain items
With Special, a curated selection of strong fish are locations that contain items
With Random Selection, a random selection of fish are locations that contain items
With All, every single fish in the game is a location that contains an item. Pairs well with the Master Angler Goal
"""
internal_name = "fishsanity"
display_name = "Fishsanity"
default = 0
option_none = 0
option_legendaries = 1
option_special = 2
option_random_selection = 3
option_all = 4
class NumberOfPlayerBuffs(Range):
"""Number of buffs to the player of each type that exist as items in the pool.
Buffs include movement speed (+25% multiplier, stacks additively)
and daily luck bonus (0.025 flat value per buff)"""
internal_name = "player_buff_number"
display_name = "Number of Player Buffs"
range_start = 0
range_end = 12
default = 4
# step = 1
class MultipleDaySleepEnabled(Toggle):
"""Should you be able to sleep automatically multiple day strait?"""
internal_name = "multiple_day_sleep_enabled"
display_name = "Multiple Day Sleep Enabled"
default = 1
class MultipleDaySleepCost(SpecialRange):
"""How must gold it cost to sleep through multiple days? You will have to pay that amount for each day slept."""
internal_name = "multiple_day_sleep_cost"
display_name = "Multiple Day Sleep Cost"
range_start = 0
range_end = 200
# step = 25
special_range_names = {
"free": 0,
"cheap": 25,
"medium": 50,
"expensive": 100,
}
class ExperienceMultiplier(SpecialRange):
"""How fast do you want to level up. A lower setting mean less experience.
A higher setting means more experience."""
internal_name = "experience_multiplier"
display_name = "Experience Multiplier"
range_start = 25
range_end = 400
# step = 25
default = 200
special_range_names = {
"half": 50,
"vanilla": 100,
"double": 200,
"triple": 300,
"quadruple": 400,
}
class DebrisMultiplier(Choice):
"""How much debris spawn on the player's farm?
With Vanilla, debris spawns normally
With Half, debris will spawn at half the normal rate
With Quarter, debris will spawn at one quarter of the normal rate
With None, No debris will spawn on the farm, ever
With Start Clear, debris will spawn at the normal rate, but the farm will be completely clear when starting the game
"""
internal_name = "debris_multiplier"
display_name = "Debris Multiplier"
default = 1
option_vanilla = 0
option_half = 1
option_quarter = 2
option_none = 3
option_start_clear = 4
class QuickStart(Toggle):
"""Do you want the quick start package? You will get a few items to help early game automation,
so you can use the multiple day sleep at its maximum."""
internal_name = "quick_start"
display_name = "Quick Start"
default = 1
class Gifting(Toggle):
"""Do you want to enable gifting items to and from other Stardew Valley worlds?"""
internal_name = "gifting"
display_name = "Gifting"
default = 1
class GiftTax(SpecialRange):
"""Joja Prime will deliver gifts within one business day, for a price!
Sending a gift will cost a percentage of the item's monetary value as a tax on the sender"""
internal_name = "gift_tax"
display_name = "Gift Tax"
range_start = 0
range_end = 400
# step = 20
default = 20
special_range_names = {
"no tax": 0,
"soft tax": 20,
"rough tax": 40,
"full tax": 100,
"oppressive tax": 200,
"nightmare tax": 400,
}
stardew_valley_options: Dict[str, type(Option)] = {
option.internal_name: option
for option in [
StartingMoney,
ResourcePackMultiplier,
BundleRandomization,
BundlePrice,
EntranceRandomization,
BackpackProgression,
ToolProgression,
SkillProgression,
BuildingProgression,
TheMinesElevatorsProgression,
ArcadeMachineLocations,
HelpWantedLocations,
Fishsanity,
NumberOfPlayerBuffs,
Goal,
MultipleDaySleepEnabled,
MultipleDaySleepCost,
ExperienceMultiplier,
DebrisMultiplier,
QuickStart,
Gifting,
GiftTax,
]
}
default_options = {option.internal_name: option.default for option in stardew_valley_options.values()}
stardew_valley_options["death_link"] = DeathLink
def fetch_options(world, player: int) -> StardewOptions:
return StardewOptions({option: get_option_value(world, player, option) for option in stardew_valley_options})
def get_option_value(world, player: int, name: str) -> Union[bool, int]:
assert name in stardew_valley_options, f"{name} is not a valid option for Stardew Valley."
value = getattr(world, name)
if issubclass(stardew_valley_options[name], Toggle):
return bool(value[player].value)
return value[player].value

View File

@@ -0,0 +1,291 @@
from dataclasses import dataclass, field
from enum import IntFlag
from random import Random
from typing import Iterable, Dict, Protocol, Optional, List, Tuple
from BaseClasses import Region, Entrance
from . import options
from .options import StardewOptions
class RegionFactory(Protocol):
def __call__(self, name: str, regions: Iterable[str]) -> Region:
raise NotImplementedError
class RandomizationFlag(IntFlag):
NOT_RANDOMIZED = 0b0
PELICAN_TOWN = 0b11111
NON_PROGRESSION = 0b11110
BUILDINGS = 0b11100
EVERYTHING = 0b11000
CHAOS = 0b10000
@dataclass(frozen=True)
class RegionData:
name: str
exits: List[str] = field(default_factory=list)
@dataclass(frozen=True)
class ConnectionData:
name: str
destination: str
reverse: Optional[str] = None
flag: RandomizationFlag = RandomizationFlag.NOT_RANDOMIZED
def __post_init__(self):
if self.reverse is None and " to " in self.name:
origin, destination = self.name.split(" to ")
super().__setattr__("reverse", f"{destination} to {origin}")
stardew_valley_regions = [
RegionData("Menu", ["To Stardew Valley"]),
RegionData("Stardew Valley", ["To Farmhouse"]),
RegionData("Farmhouse", ["Outside to Farm", "Downstairs to Cellar"]),
RegionData("Cellar"),
RegionData("Farm", ["Farm to Backwoods", "Farm to Bus Stop", "Farm to Forest", "Farm to Farmcave", "Enter Greenhouse",
"Use Desert Obelisk", "Use Island Obelisk"]),
RegionData("Backwoods", ["Backwoods to Mountain"]),
RegionData("Bus Stop", ["Bus Stop to Town", "Take Bus to Desert", "Bus Stop to Tunnel Entrance"]),
RegionData("Forest", ["Forest to Town", "Enter Secret Woods", "Forest to Wizard Tower", "Forest to Marnie's Ranch",
"Forest to Leah's Cottage", "Forest to Sewers"]),
RegionData("Farmcave"),
RegionData("Greenhouse"),
RegionData("Mountain", ["Mountain to Railroad", "Mountain to Tent", "Mountain to Carpenter Shop", "Mountain to The Mines",
"Enter Quarry", "Mountain to Adventurer's Guild", "Mountain to Town"]),
RegionData("Tunnel Entrance", ["Enter Tunnel"]),
RegionData("Tunnel"),
RegionData("Town", ["Town to Community Center", "Town to Beach", "Town to Hospital",
"Town to Pierre's General Store", "Town to Saloon", "Town to Alex's House", "Town to Trailer", "Town to Mayor's Manor",
"Town to Sam's House", "Town to Haley's House", "Town to Sewers", "Town to Clint's Blacksmith", "Town to Museum",
"Town to JojaMart"]),
RegionData("Beach", ["Beach to Willy's Fish Shop", "Enter Elliott's House", "Enter Tide Pools"]),
RegionData("Railroad", ["Enter Bathhouse Entrance", "Enter Witch Warp Cave"]), # "Enter Perfection Cutscene Area"
RegionData("Marnie's Ranch"),
RegionData("Leah's Cottage"),
RegionData("Sewers", ["Enter Mutant Bug Lair"]),
RegionData("Mutant Bug Lair"),
RegionData("Wizard Tower", ["Enter Wizard Basement"]),
RegionData("Wizard Basement"),
RegionData("Tent"),
RegionData("Carpenter Shop", ["Enter Sebastian's Room"]),
RegionData("Sebastian's Room"),
RegionData("Adventurer's Guild"),
RegionData("Community Center",
["Access Crafts Room", "Access Pantry", "Access Fish Tank", "Access Boiler Room", "Access Bulletin Board",
"Access Vault"]),
RegionData("Crafts Room"),
RegionData("Pantry"),
RegionData("Fish Tank"),
RegionData("Boiler Room"),
RegionData("Bulletin Board"),
RegionData("Vault"),
RegionData("Hospital", ["Enter Harvey's Room"]),
RegionData("Harvey's Room"),
RegionData("Pierre's General Store", ["Enter Sunroom"]),
RegionData("Sunroom"),
RegionData("Saloon", ["Play Journey of the Prairie King", "Play Junimo Kart"]),
RegionData("Alex's House"),
RegionData("Trailer"),
RegionData("Mayor's Manor"),
RegionData("Sam's House"),
RegionData("Haley's House"),
RegionData("Clint's Blacksmith"),
RegionData("Museum"),
RegionData("JojaMart"),
RegionData("Willy's Fish Shop"),
RegionData("Elliott's House"),
RegionData("Tide Pools"),
RegionData("Bathhouse Entrance", ["Enter Locker Room"]),
RegionData("Locker Room", ["Enter Public Bath"]),
RegionData("Public Bath"),
RegionData("Witch Warp Cave", ["Enter Witch's Swamp"]),
RegionData("Witch's Swamp"),
RegionData("Quarry", ["Enter Quarry Mine Entrance"]),
RegionData("Quarry Mine Entrance", ["Enter Quarry Mine"]),
RegionData("Quarry Mine"),
RegionData("Secret Woods"),
RegionData("The Desert", ["Enter Skull Cavern Entrance"]),
RegionData("Skull Cavern Entrance", ["Enter Skull Cavern"]),
RegionData("Skull Cavern"),
RegionData("Ginger Island"),
RegionData("JotPK World 1", ["Reach JotPK World 2"]),
RegionData("JotPK World 2", ["Reach JotPK World 3"]),
RegionData("JotPK World 3"),
RegionData("Junimo Kart 1", ["Reach Junimo Kart 2"]),
RegionData("Junimo Kart 2", ["Reach Junimo Kart 3"]),
RegionData("Junimo Kart 3"),
RegionData("The Mines", ["Dig to The Mines - Floor 5", "Dig to The Mines - Floor 10", "Dig to The Mines - Floor 15",
"Dig to The Mines - Floor 20", "Dig to The Mines - Floor 25", "Dig to The Mines - Floor 30",
"Dig to The Mines - Floor 35", "Dig to The Mines - Floor 40", "Dig to The Mines - Floor 45",
"Dig to The Mines - Floor 50", "Dig to The Mines - Floor 55", "Dig to The Mines - Floor 60",
"Dig to The Mines - Floor 65", "Dig to The Mines - Floor 70", "Dig to The Mines - Floor 75",
"Dig to The Mines - Floor 80", "Dig to The Mines - Floor 85", "Dig to The Mines - Floor 90",
"Dig to The Mines - Floor 95", "Dig to The Mines - Floor 100", "Dig to The Mines - Floor 105",
"Dig to The Mines - Floor 110", "Dig to The Mines - Floor 115", "Dig to The Mines - Floor 120"]),
RegionData("The Mines - Floor 5"),
RegionData("The Mines - Floor 10"),
RegionData("The Mines - Floor 15"),
RegionData("The Mines - Floor 20"),
RegionData("The Mines - Floor 25"),
RegionData("The Mines - Floor 30"),
RegionData("The Mines - Floor 35"),
RegionData("The Mines - Floor 40"),
RegionData("The Mines - Floor 45"),
RegionData("The Mines - Floor 50"),
RegionData("The Mines - Floor 55"),
RegionData("The Mines - Floor 60"),
RegionData("The Mines - Floor 65"),
RegionData("The Mines - Floor 70"),
RegionData("The Mines - Floor 75"),
RegionData("The Mines - Floor 80"),
RegionData("The Mines - Floor 85"),
RegionData("The Mines - Floor 90"),
RegionData("The Mines - Floor 95"),
RegionData("The Mines - Floor 100"),
RegionData("The Mines - Floor 105"),
RegionData("The Mines - Floor 110"),
RegionData("The Mines - Floor 115"),
RegionData("The Mines - Floor 120"),
]
# Exists and where they lead
mandatory_connections = [
ConnectionData("To Stardew Valley", "Stardew Valley"),
ConnectionData("To Farmhouse", "Farmhouse"),
ConnectionData("Outside to Farm", "Farm"),
ConnectionData("Downstairs to Cellar", "Cellar"),
ConnectionData("Farm to Backwoods", "Backwoods"),
ConnectionData("Farm to Bus Stop", "Bus Stop"),
ConnectionData("Farm to Forest", "Forest"),
ConnectionData("Farm to Farmcave", "Farmcave", flag=RandomizationFlag.NON_PROGRESSION),
ConnectionData("Enter Greenhouse", "Greenhouse"),
ConnectionData("Use Desert Obelisk", "The Desert"),
ConnectionData("Use Island Obelisk", "Ginger Island"),
ConnectionData("Backwoods to Mountain", "Mountain"),
ConnectionData("Bus Stop to Town", "Town"),
ConnectionData("Bus Stop to Tunnel Entrance", "Tunnel Entrance"),
ConnectionData("Take Bus to Desert", "The Desert"),
ConnectionData("Enter Tunnel", "Tunnel"),
ConnectionData("Forest to Town", "Town"),
ConnectionData("Forest to Wizard Tower", "Wizard Tower", flag=RandomizationFlag.NON_PROGRESSION),
ConnectionData("Enter Wizard Basement", "Wizard Basement"),
ConnectionData("Forest to Marnie's Ranch", "Marnie's Ranch", flag=RandomizationFlag.NON_PROGRESSION),
ConnectionData("Forest to Leah's Cottage", "Leah's Cottage"),
ConnectionData("Enter Secret Woods", "Secret Woods"),
ConnectionData("Forest to Sewers", "Sewers"),
ConnectionData("Town to Sewers", "Sewers"),
ConnectionData("Enter Mutant Bug Lair", "Mutant Bug Lair"),
ConnectionData("Mountain to Railroad", "Railroad"),
ConnectionData("Mountain to Tent", "Tent", flag=RandomizationFlag.NON_PROGRESSION),
ConnectionData("Mountain to Carpenter Shop", "Carpenter Shop", flag=RandomizationFlag.NON_PROGRESSION),
ConnectionData("Enter Sebastian's Room", "Sebastian's Room"),
ConnectionData("Mountain to Adventurer's Guild", "Adventurer's Guild"),
ConnectionData("Enter Quarry", "Quarry"),
ConnectionData("Enter Quarry Mine Entrance", "Quarry Mine Entrance"),
ConnectionData("Enter Quarry Mine", "Quarry Mine"),
ConnectionData("Mountain to Town", "Town"),
ConnectionData("Town to Community Center", "Community Center", flag=RandomizationFlag.PELICAN_TOWN),
ConnectionData("Access Crafts Room", "Crafts Room"),
ConnectionData("Access Pantry", "Pantry"),
ConnectionData("Access Fish Tank", "Fish Tank"),
ConnectionData("Access Boiler Room", "Boiler Room"),
ConnectionData("Access Bulletin Board", "Bulletin Board"),
ConnectionData("Access Vault", "Vault"),
ConnectionData("Town to Hospital", "Hospital", flag=RandomizationFlag.PELICAN_TOWN),
ConnectionData("Enter Harvey's Room", "Harvey's Room"),
ConnectionData("Town to Pierre's General Store", "Pierre's General Store", flag=RandomizationFlag.PELICAN_TOWN),
ConnectionData("Enter Sunroom", "Sunroom"),
ConnectionData("Town to Clint's Blacksmith", "Clint's Blacksmith", flag=RandomizationFlag.PELICAN_TOWN),
ConnectionData("Town to Saloon", "Saloon", flag=RandomizationFlag.PELICAN_TOWN),
ConnectionData("Play Journey of the Prairie King", "JotPK World 1"),
ConnectionData("Reach JotPK World 2", "JotPK World 2"),
ConnectionData("Reach JotPK World 3", "JotPK World 3"),
ConnectionData("Play Junimo Kart", "Junimo Kart 1"),
ConnectionData("Reach Junimo Kart 2", "Junimo Kart 2"),
ConnectionData("Reach Junimo Kart 3", "Junimo Kart 3"),
ConnectionData("Town to Sam's House", "Sam's House", flag=RandomizationFlag.PELICAN_TOWN),
ConnectionData("Town to Haley's House", "Haley's House", flag=RandomizationFlag.PELICAN_TOWN),
ConnectionData("Town to Mayor's Manor", "Mayor's Manor", flag=RandomizationFlag.PELICAN_TOWN),
ConnectionData("Town to Alex's House", "Alex's House", flag=RandomizationFlag.PELICAN_TOWN),
ConnectionData("Town to Trailer", "Trailer", flag=RandomizationFlag.PELICAN_TOWN),
ConnectionData("Town to Museum", "Museum", flag=RandomizationFlag.PELICAN_TOWN),
ConnectionData("Town to JojaMart", "JojaMart", flag=RandomizationFlag.PELICAN_TOWN),
ConnectionData("Town to Beach", "Beach"),
ConnectionData("Enter Elliott's House", "Elliott's House"),
ConnectionData("Beach to Willy's Fish Shop", "Willy's Fish Shop", flag=RandomizationFlag.NON_PROGRESSION),
ConnectionData("Enter Tide Pools", "Tide Pools"),
ConnectionData("Mountain to The Mines", "The Mines", flag=RandomizationFlag.NON_PROGRESSION),
ConnectionData("Dig to The Mines - Floor 5", "The Mines - Floor 5"),
ConnectionData("Dig to The Mines - Floor 10", "The Mines - Floor 10"),
ConnectionData("Dig to The Mines - Floor 15", "The Mines - Floor 15"),
ConnectionData("Dig to The Mines - Floor 20", "The Mines - Floor 20"),
ConnectionData("Dig to The Mines - Floor 25", "The Mines - Floor 25"),
ConnectionData("Dig to The Mines - Floor 30", "The Mines - Floor 30"),
ConnectionData("Dig to The Mines - Floor 35", "The Mines - Floor 35"),
ConnectionData("Dig to The Mines - Floor 40", "The Mines - Floor 40"),
ConnectionData("Dig to The Mines - Floor 45", "The Mines - Floor 45"),
ConnectionData("Dig to The Mines - Floor 50", "The Mines - Floor 50"),
ConnectionData("Dig to The Mines - Floor 55", "The Mines - Floor 55"),
ConnectionData("Dig to The Mines - Floor 60", "The Mines - Floor 60"),
ConnectionData("Dig to The Mines - Floor 65", "The Mines - Floor 65"),
ConnectionData("Dig to The Mines - Floor 70", "The Mines - Floor 70"),
ConnectionData("Dig to The Mines - Floor 75", "The Mines - Floor 75"),
ConnectionData("Dig to The Mines - Floor 80", "The Mines - Floor 80"),
ConnectionData("Dig to The Mines - Floor 85", "The Mines - Floor 85"),
ConnectionData("Dig to The Mines - Floor 90", "The Mines - Floor 90"),
ConnectionData("Dig to The Mines - Floor 95", "The Mines - Floor 95"),
ConnectionData("Dig to The Mines - Floor 100", "The Mines - Floor 100"),
ConnectionData("Dig to The Mines - Floor 105", "The Mines - Floor 105"),
ConnectionData("Dig to The Mines - Floor 110", "The Mines - Floor 110"),
ConnectionData("Dig to The Mines - Floor 115", "The Mines - Floor 115"),
ConnectionData("Dig to The Mines - Floor 120", "The Mines - Floor 120"),
ConnectionData("Enter Skull Cavern Entrance", "Skull Cavern Entrance"),
ConnectionData("Enter Skull Cavern", "Skull Cavern"),
ConnectionData("Enter Witch Warp Cave", "Witch Warp Cave"),
ConnectionData("Enter Witch's Swamp", "Witch's Swamp"),
ConnectionData("Enter Bathhouse Entrance", "Bathhouse Entrance"),
ConnectionData("Enter Locker Room", "Locker Room"),
ConnectionData("Enter Public Bath", "Public Bath"),
]
def create_regions(region_factory: RegionFactory, random: Random, world_options: StardewOptions) -> Tuple[Iterable[Region], Dict[str, str]]:
regions: Dict[str: Region] = {region.name: region_factory(region.name, region.exits) for region in stardew_valley_regions}
entrances: Dict[str: Entrance] = {entrance.name: entrance
for region in regions.values()
for entrance in region.exits}
connections, randomized_data = randomize_connections(random, world_options)
for connection in connections:
if connection.name not in entrances:
continue
entrances[connection.name].connect(regions[connection.destination])
return regions.values(), randomized_data
def randomize_connections(random: Random, world_options: StardewOptions) -> Tuple[List[ConnectionData], Dict[str, str]]:
connections_to_randomize = []
if world_options[options.EntranceRandomization] == options.EntranceRandomization.option_pelican_town:
connections_to_randomize = [connection for connection in mandatory_connections if RandomizationFlag.PELICAN_TOWN in connection.flag]
elif world_options[options.EntranceRandomization] == options.EntranceRandomization.option_non_progression:
connections_to_randomize = [connection for connection in mandatory_connections if RandomizationFlag.NON_PROGRESSION in connection.flag]
random.shuffle(connections_to_randomize)
destination_pool = list(connections_to_randomize)
random.shuffle(destination_pool)
randomized_connections = []
randomized_data = {}
for connection in connections_to_randomize:
destination = destination_pool.pop()
randomized_connections.append(ConnectionData(connection.name, destination.destination, destination.reverse))
randomized_data[connection.name] = destination.name
randomized_data[destination.reverse] = connection.reverse
return mandatory_connections, randomized_data

View File

@@ -0,0 +1 @@
importlib_resources; python_version <= '3.8'

View File

@@ -0,0 +1,190 @@
import itertools
from typing import Dict
from BaseClasses import MultiWorld
from worlds.generic import Rules as MultiWorldRules
from . import options, locations
from .bundles import Bundle
from .locations import LocationTags
from .logic import StardewLogic, _And, season_per_skill_level, tool_prices, week_days
help_wanted_per_season = {
1: "Spring",
2: "Summer",
3: "Fall",
4: "Winter",
5: "Year Two",
6: "Year Two",
7: "Year Two",
8: "Year Two",
9: "Year Two",
10: "Year Two",
}
def set_rules(multi_world: MultiWorld, player: int, world_options: options.StardewOptions, logic: StardewLogic,
current_bundles: Dict[str, Bundle]):
summer = multi_world.get_location("Summer", player)
all_location_names = list(location.name for location in multi_world.get_locations(player))
for floor in range(5, 120 + 5, 5):
MultiWorldRules.add_rule(multi_world.get_entrance(f"Dig to The Mines - Floor {floor}", player),
logic.can_mine_to_floor(floor).simplify())
MultiWorldRules.add_rule(multi_world.get_entrance("Enter Quarry", player),
logic.received("Bridge Repair").simplify())
MultiWorldRules.add_rule(multi_world.get_entrance("Enter Secret Woods", player),
logic.has_tool("Axe", "Iron").simplify())
MultiWorldRules.add_rule(multi_world.get_entrance("Take Bus to Desert", player),
logic.received("Bus Repair").simplify())
MultiWorldRules.add_rule(multi_world.get_entrance("Enter Skull Cavern", player),
logic.received("Skull Key").simplify())
MultiWorldRules.add_rule(multi_world.get_entrance("Use Desert Obelisk", player),
logic.received("Desert Obelisk").simplify())
MultiWorldRules.add_rule(multi_world.get_entrance("Use Island Obelisk", player),
logic.received("Island Obelisk").simplify())
# Those checks do not exist if ToolProgression is vanilla
if world_options[options.ToolProgression] != options.ToolProgression.option_vanilla:
MultiWorldRules.add_rule(multi_world.get_location("Purchase Fiberglass Rod", player),
(logic.has_skill_level("Fishing", 2) & logic.can_spend_money(1800)).simplify())
MultiWorldRules.add_rule(multi_world.get_location("Purchase Iridium Rod", player),
(logic.has_skill_level("Fishing", 6) & logic.can_spend_money(7500)).simplify())
materials = [None, "Copper", "Iron", "Gold", "Iridium"]
tool = ["Hoe", "Pickaxe", "Axe", "Watering Can", "Trash Can"]
for (previous, material), tool in itertools.product(zip(materials[:4], materials[1:]), tool):
if previous is None:
MultiWorldRules.add_rule(multi_world.get_location(f"{material} {tool} Upgrade", player),
(logic.has(f"{material} Ore") &
logic.can_spend_money(tool_prices[material])).simplify())
else:
MultiWorldRules.add_rule(multi_world.get_location(f"{material} {tool} Upgrade", player),
(logic.has(f"{material} Ore") & logic.has_tool(tool, previous) &
logic.can_spend_money(tool_prices[material])).simplify())
# Skills
if world_options[options.SkillProgression] != options.SkillProgression.option_vanilla:
for i in range(1, 11):
MultiWorldRules.set_rule(multi_world.get_location(f"Level {i} Farming", player),
(logic.received(season_per_skill_level["Farming", i])).simplify())
MultiWorldRules.set_rule(multi_world.get_location(f"Level {i} Fishing", player),
(logic.can_get_fishing_xp() &
logic.received(season_per_skill_level["Fishing", i])).simplify())
MultiWorldRules.add_rule(multi_world.get_location(f"Level {i} Foraging", player),
logic.received(season_per_skill_level["Foraging", i]).simplify())
if i >= 6:
MultiWorldRules.add_rule(multi_world.get_location(f"Level {i} Foraging", player),
logic.has_tool("Axe", "Iron").simplify())
MultiWorldRules.set_rule(multi_world.get_location(f"Level {i} Mining", player),
logic.received(season_per_skill_level["Mining", i]).simplify())
MultiWorldRules.set_rule(multi_world.get_location(f"Level {i} Combat", player),
(logic.received(season_per_skill_level["Combat", i]) &
logic.has_any_weapon()).simplify())
# Bundles
for bundle in current_bundles.values():
MultiWorldRules.set_rule(multi_world.get_location(bundle.get_name_with_bundle(), player),
logic.can_complete_bundle(bundle.requirements, bundle.number_required).simplify())
MultiWorldRules.add_rule(multi_world.get_location("Complete Crafts Room", player),
_And(logic.can_reach_location(bundle.name)
for bundle in locations.locations_by_tag[LocationTags.CRAFTS_ROOM_BUNDLE]).simplify())
MultiWorldRules.add_rule(multi_world.get_location("Complete Pantry", player),
_And(logic.can_reach_location(bundle.name)
for bundle in locations.locations_by_tag[LocationTags.PANTRY_BUNDLE]).simplify())
MultiWorldRules.add_rule(multi_world.get_location("Complete Fish Tank", player),
_And(logic.can_reach_location(bundle.name)
for bundle in locations.locations_by_tag[LocationTags.FISH_TANK_BUNDLE]).simplify())
MultiWorldRules.add_rule(multi_world.get_location("Complete Boiler Room", player),
_And(logic.can_reach_location(bundle.name)
for bundle in locations.locations_by_tag[LocationTags.BOILER_ROOM_BUNDLE]).simplify())
MultiWorldRules.add_rule(multi_world.get_location("Complete Bulletin Board", player),
_And(logic.can_reach_location(bundle.name)
for bundle in locations.locations_by_tag[LocationTags.BULLETIN_BOARD_BUNDLE]).simplify())
MultiWorldRules.add_rule(multi_world.get_location("Complete Vault", player),
_And(logic.can_reach_location(bundle.name)
for bundle in locations.locations_by_tag[LocationTags.VAULT_BUNDLE]).simplify())
# Buildings
if world_options[options.BuildingProgression] != options.BuildingProgression.option_vanilla:
for building in locations.locations_by_tag[LocationTags.BUILDING_BLUEPRINT]:
MultiWorldRules.set_rule(multi_world.get_location(building.name, player),
logic.building_rules[building.name.replace(" Blueprint", "")].simplify())
# Story Quests
for quest in locations.locations_by_tag[LocationTags.QUEST]:
MultiWorldRules.set_rule(multi_world.get_location(quest.name, player),
logic.quest_rules[quest.name].simplify())
# Help Wanted Quests
desired_number_help_wanted: int = world_options[options.HelpWantedLocations] // 7
for i in range(1, desired_number_help_wanted + 1):
prefix = "Help Wanted:"
delivery = "Item Delivery"
rule = logic.received(help_wanted_per_season[min(5, i)])
fishing_rule = rule & logic.can_fish()
slay_rule = rule & logic.has_any_weapon()
for j in range(i, i + 4):
MultiWorldRules.set_rule(multi_world.get_location(f"{prefix} {delivery} {j}", player),
rule.simplify())
MultiWorldRules.set_rule(multi_world.get_location(f"{prefix} Gathering {i}", player),
rule.simplify())
MultiWorldRules.set_rule(multi_world.get_location(f"{prefix} Fishing {i}", player),
fishing_rule.simplify())
MultiWorldRules.set_rule(multi_world.get_location(f"{prefix} Slay Monsters {i}", player),
slay_rule.simplify())
fish_prefix = "Fishsanity: "
for fish_location in locations.locations_by_tag[LocationTags.FISHSANITY]:
if fish_location.name in all_location_names:
fish_name = fish_location.name[len(fish_prefix):]
MultiWorldRules.set_rule(multi_world.get_location(fish_location.name, player),
logic.has(fish_name).simplify())
if world_options[options.BuildingProgression] == options.BuildingProgression.option_progressive_early_shipping_bin:
summer.access_rule = summer.access_rule & logic.received("Shipping Bin")
# Backpacks
if world_options[options.BackpackProgression] != options.BackpackProgression.option_vanilla:
MultiWorldRules.add_rule(multi_world.get_location("Large Pack", player),
logic.can_spend_money(2000).simplify())
MultiWorldRules.add_rule(multi_world.get_location("Deluxe Pack", player),
logic.can_spend_money(10000).simplify())
if world_options[options.BackpackProgression] == options.BackpackProgression.option_early_progressive:
summer.access_rule = summer.access_rule & logic.received("Progressive Backpack")
MultiWorldRules.add_rule(multi_world.get_location("Winter", player),
logic.received("Progressive Backpack", 2).simplify())
MultiWorldRules.add_rule(multi_world.get_location("Old Master Cannoli", player),
logic.has("Sweet Gem Berry").simplify())
MultiWorldRules.add_rule(multi_world.get_location("Galaxy Sword Shrine", player),
logic.has("Prismatic Shard").simplify())
# Traveling Merchant
for day in week_days:
item_for_day = f"Traveling Merchant: {day}"
for i in range(1, 4):
location_name = f"Traveling Merchant {day} Item {i}"
MultiWorldRules.set_rule(multi_world.get_location(location_name, player),
logic.received(item_for_day))
if world_options[options.ArcadeMachineLocations] == options.ArcadeMachineLocations.option_full_shuffling:
MultiWorldRules.add_rule(multi_world.get_entrance("Play Junimo Kart", player),
(logic.received("Skull Key") & logic.has("Junimo Kart Small Buff")).simplify())
MultiWorldRules.add_rule(multi_world.get_entrance("Reach Junimo Kart 2", player),
logic.has("Junimo Kart Medium Buff").simplify())
MultiWorldRules.add_rule(multi_world.get_entrance("Reach Junimo Kart 3", player),
logic.has("Junimo Kart Big Buff").simplify())
MultiWorldRules.add_rule(multi_world.get_location("Junimo Kart: Sunset Speedway (Victory)", player),
logic.has("Junimo Kart Max Buff").simplify())
MultiWorldRules.add_rule(multi_world.get_entrance("Play Journey of the Prairie King", player),
logic.has("JotPK Small Buff").simplify())
MultiWorldRules.add_rule(multi_world.get_entrance("Reach JotPK World 2", player),
logic.has("JotPK Medium Buff").simplify())
MultiWorldRules.add_rule(multi_world.get_entrance("Reach JotPK World 3", player),
logic.has("JotPK Big Buff").simplify())
MultiWorldRules.add_rule(multi_world.get_location("Journey of the Prairie King Victory", player),
logic.has("JotPK Max Buff").simplify())

View File

@@ -0,0 +1,26 @@
"""Items export script
This script can be used to export all the AP items into a json file in the output folder. This file is used by the tests
of the mod to ensure it can handle all possible items.
To run the script, use `python -m worlds.stardew_valley.scripts.export_items` from the repository root.
"""
import json
import os.path
from worlds.stardew_valley import item_table
if not os.path.isdir("output"):
os.mkdir("output")
if __name__ == "__main__":
with open("output/stardew_valley_item_table.json", "w+") as f:
items = {
item.name: {
"code": item.code,
"classification": item.classification.name
}
for item in item_table.values()
if item.code is not None
}
json.dump({"items": items}, f)

View File

@@ -0,0 +1,26 @@
"""Locations export script
This script can be used to export all the AP locations into a json file in the output folder. This file is used by the
tests of the mod to ensure it can handle all possible locations.
To run the script, use `python -m worlds.stardew_valley.scripts.export_locations` from the repository root.
"""
import json
import os
from worlds.stardew_valley import location_table
if not os.path.isdir("output"):
os.mkdir("output")
if __name__ == "__main__":
with open("output/stardew_valley_location_table.json", "w+") as f:
locations = {
location.name: {
"code": location.code,
"region": location.region,
}
for location in location_table.values()
if location.code is not None
}
json.dump({"locations": locations}, f)

View File

@@ -0,0 +1,88 @@
"""Update data script
This script can be used to assign new ids for the items and locations in the CSV file. It also regenerates the items
based on the resource packs.
To run the script, use `python -m worlds.stardew_valley.scripts.update_data` from the repository root.
"""
import csv
import itertools
from pathlib import Path
from typing import List
from worlds.stardew_valley import LocationData
from worlds.stardew_valley.items import load_item_csv, Group, ItemData, load_resource_pack_csv, friendship_pack
from worlds.stardew_valley.locations import load_location_csv
RESOURCE_PACK_CODE_OFFSET = 5000
script_folder = Path(__file__)
def write_item_csv(items: List[ItemData]):
with open((script_folder.parent.parent / "data/items.csv").resolve(), "w", newline="") as file:
writer = csv.DictWriter(file, ["id", "name", "classification", "groups"])
writer.writeheader()
for item in items:
item_dict = {
"id": item.code_without_offset,
"name": item.name,
"classification": item.classification.name,
"groups": ",".join(sorted(group.name for group in item.groups))
}
writer.writerow(item_dict)
def write_location_csv(locations: List[LocationData]):
with open((script_folder.parent.parent / "data/locations.csv").resolve(), "w", newline="") as file:
write = csv.DictWriter(file, ["id", "region", "name", "tags"])
write.writeheader()
for location in locations:
location_dict = {
"id": location.code_without_offset,
"name": location.name,
"region": location.region,
"tags": ",".join(sorted(group.name for group in location.tags))
}
write.writerow(location_dict)
if __name__ == "__main__":
loaded_items = load_item_csv()
item_counter = itertools.count(max(item.code_without_offset
for item in loaded_items
if Group.RESOURCE_PACK not in item.groups
and item.code_without_offset is not None) + 1)
items_to_write = []
for item in loaded_items:
if item.has_any_group(Group.RESOURCE_PACK, Group.FRIENDSHIP_PACK):
continue
if item.code_without_offset is None:
items_to_write.append(ItemData(next(item_counter), item.name, item.classification, item.groups))
continue
items_to_write.append(item)
all_resource_packs = load_resource_pack_csv() + [friendship_pack]
resource_pack_counter = itertools.count(RESOURCE_PACK_CODE_OFFSET)
items_to_write.extend(
item for resource_pack in all_resource_packs for item in resource_pack.as_item_data(resource_pack_counter))
write_item_csv(items_to_write)
loaded_locations = load_location_csv()
location_counter = itertools.count(max(location.code_without_offset
for location in loaded_locations
if location.code_without_offset is not None) + 1)
locations_to_write = []
for location in loaded_locations:
if location.code_without_offset is None:
locations_to_write.append(
LocationData(next(location_counter), location.region, location.name, location.tags))
continue
locations_to_write.append(location)
write_location_csv(locations_to_write)

View File

@@ -0,0 +1,53 @@
import unittest
from test.general import setup_solo_multiworld
from .. import StardewValleyWorld
from ..bundle_data import all_bundle_items_except_money
from ..logic import MISSING_ITEM, _False
class TestAllLogicalItem(unittest.TestCase):
multi_world = setup_solo_multiworld(StardewValleyWorld)
world = multi_world.worlds[1]
logic = world.logic
def setUp(self) -> None:
for item in self.multi_world.get_items():
self.multi_world.state.collect(item, event=True)
def test_given_bundle_item_then_is_available_in_logic(self):
for bundle_item in all_bundle_items_except_money:
with self.subTest(bundle_item=bundle_item):
assert bundle_item.item.name in self.logic.item_rules
def test_given_item_rule_then_can_be_resolved(self):
for item in self.logic.item_rules.keys():
with self.subTest(item=item):
rule = self.logic.item_rules[item]
assert MISSING_ITEM not in repr(rule)
assert rule == _False() or rule(self.multi_world.state), f"Could not resolve rule for {item} {rule}"
def test_given_building_rule_then_can_be_resolved(self):
for item in self.logic.building_rules.keys():
with self.subTest(item=item):
rule = self.logic.building_rules[item]
assert MISSING_ITEM not in repr(rule)
assert rule == _False() or rule(self.multi_world.state), f"Could not resolve rule for {item} {rule}"
def test_given_quest_rule_then_can_be_resolved(self):
for item in self.logic.quest_rules.keys():
with self.subTest(item=item):
rule = self.logic.quest_rules[item]
assert MISSING_ITEM not in repr(rule)
assert rule == _False() or rule(self.multi_world.state), f"Could not resolve rule for {item} {rule}"
def test_given_location_rule_then_can_be_resolved(self):
for location in self.multi_world.get_locations(1):
with self.subTest(location=location):
rule = location.access_rule
assert MISSING_ITEM not in repr(rule)
assert rule == _False() or rule(self.multi_world.state), f"Could not resolve rule for {location} {rule}"

View File

@@ -0,0 +1,16 @@
import unittest
from ..bundle_data import all_bundle_items
class TestBundles(unittest.TestCase):
def test_all_bundle_items_have_3_parts(self):
for bundle_item in all_bundle_items:
name = bundle_item.item.name
assert len(name) > 0
id = bundle_item.item.item_id
assert (id > 0 or id == -1)
amount = bundle_item.amount
assert amount > 0
quality = bundle_item.quality
assert quality >= 0

View File

@@ -0,0 +1,20 @@
import unittest
from ..items import load_item_csv
from ..locations import load_location_csv
class TestCsvIntegrity(unittest.TestCase):
def test_items_integrity(self):
items = load_item_csv()
for item in items:
assert item.code_without_offset is not None, \
"Some item do not have an id. Run the script `update_data.py` to generate them."
def test_locations_integrity(self):
locations = load_location_csv()
for location in locations:
assert location.code_without_offset is not None, \
"Some location do not have an id. Run the script `update_data.py` to generate them."

View File

@@ -0,0 +1,127 @@
from BaseClasses import ItemClassification
from . import SVTestBase
from .. import locations, items, location_table, options
from ..items import items_by_group, Group
from ..locations import LocationTags
class TestBaseItemGeneration(SVTestBase):
def test_all_progression_items_are_added_to_the_pool(self):
for classification in [ItemClassification.progression, ItemClassification.useful]:
with self.subTest(classification=classification):
all_classified_items = {self.world.create_item(item)
for item in items.items_by_group[items.Group.COMMUNITY_REWARD]
if item.classification is classification}
for item in all_classified_items:
assert item in self.multiworld.itempool
def test_creates_as_many_item_as_non_event_locations(self):
non_event_locations = [location for location in self.multiworld.get_locations(self.player) if
not location.event]
assert len(non_event_locations), len(self.multiworld.itempool)
class TestGivenProgressiveBackpack(SVTestBase):
options = {options.BackpackProgression.internal_name: options.BackpackProgression.option_progressive}
def test_when_generate_world_then_two_progressive_backpack_are_added(self):
assert self.multiworld.itempool.count(self.world.create_item("Progressive Backpack")) == 2
def test_when_generate_world_then_backpack_locations_are_added(self):
created_locations = {location.name for location in self.multiworld.get_locations(1)}
assert all(location.name in created_locations for location in locations.locations_by_tag[LocationTags.BACKPACK])
class TestRemixedMineRewards(SVTestBase):
def test_when_generate_world_then_one_reward_is_added_per_chest(self):
# assert self.world.create_item("Rusty Sword") in self.multiworld.itempool
assert any(self.world.create_item(item) in self.multiworld.itempool
for item in items_by_group[Group.MINES_FLOOR_10])
assert any(self.world.create_item(item) in self.multiworld.itempool
for item in items_by_group[Group.MINES_FLOOR_20])
assert self.world.create_item("Slingshot") in self.multiworld.itempool
assert any(self.world.create_item(item) in self.multiworld.itempool
for item in items_by_group[Group.MINES_FLOOR_50])
assert any(self.world.create_item(item) in self.multiworld.itempool
for item in items_by_group[Group.MINES_FLOOR_60])
assert self.world.create_item("Master Slingshot") in self.multiworld.itempool
assert any(self.world.create_item(item) in self.multiworld.itempool
for item in items_by_group[Group.MINES_FLOOR_80])
assert any(self.world.create_item(item) in self.multiworld.itempool
for item in items_by_group[Group.MINES_FLOOR_90])
assert self.world.create_item("Stardrop") in self.multiworld.itempool
assert any(self.world.create_item(item) in self.multiworld.itempool
for item in items_by_group[Group.MINES_FLOOR_110])
assert self.world.create_item("Skull Key") in self.multiworld.itempool
# This test as 1 over 90,000 changes to fail... Sorry in advance
def test_when_generate_world_then_rewards_are_not_all_vanilla(self):
assert not all(self.world.create_item(item) in self.multiworld.itempool
for item in
["Leather Boots", "Steel Smallsword", "Tundra Boots", "Crystal Dagger", "Firewalker Boots",
"Obsidian Edge", "Space Boots"])
class TestProgressiveElevator(SVTestBase):
options = {
options.TheMinesElevatorsProgression.internal_name: options.TheMinesElevatorsProgression.option_progressive,
options.ToolProgression.internal_name: options.ToolProgression.option_progressive,
options.SkillProgression.internal_name: options.SkillProgression.option_progressive,
}
def test_given_access_to_floor_115_when_find_another_elevator_then_has_access_to_floor_120(self):
self.collect([self.get_item_by_name("Progressive Pickaxe")] * 2)
self.collect([self.get_item_by_name("Progressive Mine Elevator")] * 22)
self.collect(self.multiworld.create_item("Bone Sword", self.player))
self.collect([self.get_item_by_name("Combat Level")] * 4)
self.collect(self.get_item_by_name("Adventurer's Guild"))
assert not self.multiworld.get_region("The Mines - Floor 120", self.player).can_reach(self.multiworld.state)
self.collect(self.get_item_by_name("Progressive Mine Elevator"))
assert self.multiworld.get_region("The Mines - Floor 120", self.player).can_reach(self.multiworld.state)
def test_given_access_to_floor_115_when_find_another_pickaxe_and_sword_then_has_access_to_floor_120(self):
self.collect([self.get_item_by_name("Progressive Pickaxe")] * 2)
self.collect([self.get_item_by_name("Progressive Mine Elevator")] * 22)
self.collect(self.multiworld.create_item("Bone Sword", self.player))
self.collect([self.get_item_by_name("Combat Level")] * 4)
self.collect(self.get_item_by_name("Adventurer's Guild"))
assert not self.multiworld.get_region("The Mines - Floor 120", self.player).can_reach(self.multiworld.state)
self.collect(self.get_item_by_name("Progressive Pickaxe"))
self.collect(self.multiworld.create_item("Steel Falchion", self.player))
self.collect(self.get_item_by_name("Combat Level"))
self.collect(self.get_item_by_name("Combat Level"))
assert self.multiworld.get_region("The Mines - Floor 120", self.player).can_reach(self.multiworld.state)
class TestLocationGeneration(SVTestBase):
def test_all_location_created_are_in_location_table(self):
for location in self.multiworld.get_locations(self.player):
if not location.event:
assert location.name in location_table
class TestLocationAndItemCount(SVTestBase):
options = {
options.BackpackProgression.internal_name: options.BackpackProgression.option_vanilla,
options.ToolProgression.internal_name: options.ToolProgression.option_vanilla,
options.TheMinesElevatorsProgression.internal_name: options.TheMinesElevatorsProgression.option_vanilla,
options.SkillProgression.internal_name: options.SkillProgression.option_vanilla,
options.BuildingProgression.internal_name: options.BuildingProgression.option_vanilla,
options.ArcadeMachineLocations.internal_name: options.ArcadeMachineLocations.option_disabled,
options.HelpWantedLocations.internal_name: 0,
options.NumberOfPlayerBuffs.internal_name: 12,
}
def test_minimal_location_maximal_items_still_valid(self):
assert len(self.multiworld.get_locations()) >= len(self.multiworld.get_items())

View File

@@ -0,0 +1,26 @@
import unittest
from BaseClasses import MultiWorld
from .. import StardewValleyWorld
from ..items import item_table
class TestItems(unittest.TestCase):
def test_can_create_item_of_resource_pack(self):
item_name = "Resource Pack: 500 Money"
multi_world = MultiWorld(1)
multi_world.game[1] = "Stardew Valley"
multi_world.player_name = {1: "Tester"}
world = StardewValleyWorld(multi_world, 1)
item = world.create_item(item_name)
assert item.name == item_name
def test_items_table_footprint_is_between_717000_and_727000(self):
item_with_lowest_id = min((item for item in item_table.values() if item.code is not None), key=lambda x: x.code)
item_with_highest_id = max((item for item in item_table.values() if item.code is not None),
key=lambda x: x.code)
assert item_with_lowest_id.code >= 717000
assert item_with_highest_id.code < 727000

View File

@@ -0,0 +1,293 @@
from . import SVTestBase
from .. import options
class TestProgressiveToolsLogic(SVTestBase):
options = {
options.ToolProgression.internal_name: options.ToolProgression.option_progressive,
}
def test_sturgeon(self):
assert not self.world.logic.has("Sturgeon")(self.multiworld.state)
summer = self.get_item_by_name("Summer")
self.multiworld.state.collect(summer, event=True)
assert not self.world.logic.has("Sturgeon")(self.multiworld.state)
fishing_rod = self.get_item_by_name("Progressive Fishing Rod")
self.multiworld.state.collect(fishing_rod, event=True)
self.multiworld.state.collect(fishing_rod, event=True)
assert not self.world.logic.has("Sturgeon")(self.multiworld.state)
fishing_level = self.get_item_by_name("Fishing Level")
self.multiworld.state.collect(fishing_level, event=True)
assert not self.world.logic.has("Sturgeon")(self.multiworld.state)
self.multiworld.state.collect(fishing_level, event=True)
self.multiworld.state.collect(fishing_level, event=True)
self.multiworld.state.collect(fishing_level, event=True)
self.multiworld.state.collect(fishing_level, event=True)
self.multiworld.state.collect(fishing_level, event=True)
assert self.world.logic.has("Sturgeon")(self.multiworld.state)
self.remove(summer)
assert not self.world.logic.has("Sturgeon")(self.multiworld.state)
winter = self.get_item_by_name("Winter")
self.multiworld.state.collect(winter, event=True)
assert self.world.logic.has("Sturgeon")(self.multiworld.state)
self.remove(fishing_rod)
assert not self.world.logic.has("Sturgeon")(self.multiworld.state)
def test_old_master_cannoli(self):
self.multiworld.state.collect(self.get_item_by_name("Progressive Axe"), event=True)
self.multiworld.state.collect(self.get_item_by_name("Progressive Axe"), event=True)
assert not self.world.logic.can_reach_location("Old Master Cannoli")(self.multiworld.state)
fall = self.get_item_by_name("Fall")
self.multiworld.state.collect(fall, event=True)
assert not self.world.logic.can_reach_location("Old Master Cannoli")(self.multiworld.state)
tuesday = self.get_item_by_name("Traveling Merchant: Tuesday")
self.multiworld.state.collect(tuesday, event=True)
assert self.world.logic.can_reach_location("Old Master Cannoli")(self.multiworld.state)
self.remove(fall)
assert not self.world.logic.can_reach_location("Old Master Cannoli")(self.multiworld.state)
self.remove(tuesday)
green_house = self.get_item_by_name("Greenhouse")
self.multiworld.state.collect(green_house, event=True)
assert not self.world.logic.can_reach_location("Old Master Cannoli")(self.multiworld.state)
friday = self.get_item_by_name("Traveling Merchant: Friday")
self.multiworld.state.collect(friday, event=True)
assert self.world.logic.can_reach_location("Old Master Cannoli")(self.multiworld.state)
self.remove(green_house)
assert not self.world.logic.can_reach_location("Old Master Cannoli")(self.multiworld.state)
self.remove(friday)
class TestBundlesLogic(SVTestBase):
options = {
}
def test_vault_2500g_bundle(self):
assert not self.world.logic.can_reach_location("2,500g Bundle")(self.multiworld.state)
summer = self.get_item_by_name("Summer")
self.multiworld.state.collect(summer, event=True)
assert self.world.logic.can_reach_location("2,500g Bundle")(self.multiworld.state)
class TestBuildingLogic(SVTestBase):
options = {
options.BuildingProgression.internal_name: options.BuildingProgression.option_progressive_early_shipping_bin
}
def test_coop_blueprint(self):
assert not self.world.logic.can_reach_location("Coop Blueprint")(self.multiworld.state)
summer = self.get_item_by_name("Summer")
self.multiworld.state.collect(summer, event=True)
assert self.world.logic.can_reach_location("Coop Blueprint")(self.multiworld.state)
def test_big_coop_blueprint(self):
assert not self.world.logic.can_reach_location("Big Coop Blueprint")(self.multiworld.state), \
f"Rule is {repr(self.multiworld.get_location('Big Coop Blueprint', self.player).access_rule)}"
self.multiworld.state.collect(self.get_item_by_name("Fall"), event=True)
assert not self.world.logic.can_reach_location("Big Coop Blueprint")(self.multiworld.state), \
f"Rule is {repr(self.multiworld.get_location('Big Coop Blueprint', self.player).access_rule)}"
self.multiworld.state.collect(self.get_item_by_name("Progressive Coop"), event=True)
assert self.world.logic.can_reach_location("Big Coop Blueprint")(self.multiworld.state), \
f"Rule is {repr(self.multiworld.get_location('Big Coop Blueprint', self.player).access_rule)}"
def test_deluxe_big_coop_blueprint(self):
assert not self.world.logic.can_reach_location("Deluxe Coop Blueprint")(self.multiworld.state)
self.multiworld.state.collect(self.get_item_by_name("Year Two"), event=True)
assert not self.world.logic.can_reach_location("Deluxe Coop Blueprint")(self.multiworld.state)
self.multiworld.state.collect(self.get_item_by_name("Progressive Coop"), event=True)
assert not self.world.logic.can_reach_location("Deluxe Coop Blueprint")(self.multiworld.state)
self.multiworld.state.collect(self.get_item_by_name("Progressive Coop"), event=True)
assert self.world.logic.can_reach_location("Deluxe Coop Blueprint")(self.multiworld.state)
def test_big_shed_blueprint(self):
assert not self.world.logic.can_reach_location("Big Shed Blueprint")(self.multiworld.state), \
f"Rule is {repr(self.multiworld.get_location('Big Shed Blueprint', self.player).access_rule)}"
self.multiworld.state.collect(self.get_item_by_name("Year Two"), event=True)
assert not self.world.logic.can_reach_location("Big Shed Blueprint")(self.multiworld.state), \
f"Rule is {repr(self.multiworld.get_location('Big Shed Blueprint', self.player).access_rule)}"
self.multiworld.state.collect(self.get_item_by_name("Progressive Shed"), event=True)
assert self.world.logic.can_reach_location("Big Shed Blueprint")(self.multiworld.state), \
f"Rule is {repr(self.multiworld.get_location('Big Shed Blueprint', self.player).access_rule)}"
class TestArcadeMachinesLogic(SVTestBase):
options = {
options.ArcadeMachineLocations.internal_name: options.ArcadeMachineLocations.option_full_shuffling,
}
def test_prairie_king(self):
assert not self.world.logic.can_reach_region("JotPK World 1")(self.multiworld.state)
assert not self.world.logic.can_reach_region("JotPK World 2")(self.multiworld.state)
assert not self.world.logic.can_reach_region("JotPK World 3")(self.multiworld.state)
assert not self.world.logic.can_reach_location("Journey of the Prairie King Victory")(self.multiworld.state)
boots = self.get_item_by_name("JotPK: Progressive Boots")
gun = self.get_item_by_name("JotPK: Progressive Gun")
ammo = self.get_item_by_name("JotPK: Progressive Ammo")
life = self.get_item_by_name("JotPK: Extra Life")
drop = self.get_item_by_name("JotPK: Increased Drop Rate")
self.multiworld.state.collect(boots, event=True)
self.multiworld.state.collect(gun, event=True)
assert self.world.logic.can_reach_region("JotPK World 1")(self.multiworld.state)
assert not self.world.logic.can_reach_region("JotPK World 2")(self.multiworld.state)
assert not self.world.logic.can_reach_region("JotPK World 3")(self.multiworld.state)
assert not self.world.logic.can_reach_location("Journey of the Prairie King Victory")(self.multiworld.state)
self.remove(boots)
self.remove(gun)
self.multiworld.state.collect(boots, event=True)
self.multiworld.state.collect(boots, event=True)
assert self.world.logic.can_reach_region("JotPK World 1")(self.multiworld.state)
assert not self.world.logic.can_reach_region("JotPK World 2")(self.multiworld.state)
assert not self.world.logic.can_reach_region("JotPK World 3")(self.multiworld.state)
assert not self.world.logic.can_reach_location("Journey of the Prairie King Victory")(self.multiworld.state)
self.remove(boots)
self.remove(boots)
self.multiworld.state.collect(boots, event=True)
self.multiworld.state.collect(gun, event=True)
self.multiworld.state.collect(ammo, event=True)
self.multiworld.state.collect(life, event=True)
assert self.world.logic.can_reach_region("JotPK World 1")(self.multiworld.state)
assert self.world.logic.can_reach_region("JotPK World 2")(self.multiworld.state)
assert not self.world.logic.can_reach_region("JotPK World 3")(self.multiworld.state)
assert not self.world.logic.can_reach_location("Journey of the Prairie King Victory")(self.multiworld.state)
self.remove(boots)
self.remove(gun)
self.remove(ammo)
self.remove(life)
self.multiworld.state.collect(boots, event=True)
self.multiworld.state.collect(gun, event=True)
self.multiworld.state.collect(gun, event=True)
self.multiworld.state.collect(ammo, event=True)
self.multiworld.state.collect(ammo, event=True)
self.multiworld.state.collect(life, event=True)
self.multiworld.state.collect(drop, event=True)
assert self.world.logic.can_reach_region("JotPK World 1")(self.multiworld.state)
assert self.world.logic.can_reach_region("JotPK World 2")(self.multiworld.state)
assert self.world.logic.can_reach_region("JotPK World 3")(self.multiworld.state)
assert not self.world.logic.can_reach_location("Journey of the Prairie King Victory")(self.multiworld.state)
self.remove(boots)
self.remove(gun)
self.remove(gun)
self.remove(ammo)
self.remove(ammo)
self.remove(life)
self.remove(drop)
self.multiworld.state.collect(boots, event=True)
self.multiworld.state.collect(boots, event=True)
self.multiworld.state.collect(gun, event=True)
self.multiworld.state.collect(gun, event=True)
self.multiworld.state.collect(gun, event=True)
self.multiworld.state.collect(gun, event=True)
self.multiworld.state.collect(ammo, event=True)
self.multiworld.state.collect(ammo, event=True)
self.multiworld.state.collect(ammo, event=True)
self.multiworld.state.collect(life, event=True)
self.multiworld.state.collect(drop, event=True)
assert self.world.logic.can_reach_region("JotPK World 1")(self.multiworld.state)
assert self.world.logic.can_reach_region("JotPK World 2")(self.multiworld.state)
assert self.world.logic.can_reach_region("JotPK World 3")(self.multiworld.state)
assert self.world.logic.can_reach_location("Journey of the Prairie King Victory")(self.multiworld.state)
self.remove(boots)
self.remove(boots)
self.remove(gun)
self.remove(gun)
self.remove(gun)
self.remove(gun)
self.remove(ammo)
self.remove(ammo)
self.remove(ammo)
self.remove(life)
self.remove(drop)
class TestWeaponsLogic(SVTestBase):
options = {
options.ToolProgression.internal_name: options.ToolProgression.option_progressive,
options.SkillProgression.internal_name: options.SkillProgression.option_progressive,
}
def test_mine(self):
self.collect(self.get_item_by_name("Adventurer's Guild"))
self.multiworld.state.collect(self.get_item_by_name("Progressive Pickaxe"), event=True)
self.multiworld.state.collect(self.get_item_by_name("Progressive Pickaxe"), event=True)
self.multiworld.state.collect(self.get_item_by_name("Progressive Pickaxe"), event=True)
self.multiworld.state.collect(self.get_item_by_name("Progressive Pickaxe"), event=True)
self.collect([self.get_item_by_name("Combat Level")] * 10)
self.collect([self.get_item_by_name("Progressive Mine Elevator")] * 24)
self.multiworld.state.collect(self.get_item_by_name("Bus Repair"), event=True)
self.multiworld.state.collect(self.get_item_by_name("Skull Key"), event=True)
self.GiveItemAndCheckReachableMine("Rusty Sword", 1)
self.GiveItemAndCheckReachableMine("Wooden Blade", 1)
self.GiveItemAndCheckReachableMine("Elf Blade", 1)
self.GiveItemAndCheckReachableMine("Silver Saber", 2)
self.GiveItemAndCheckReachableMine("Crystal Dagger", 2)
self.GiveItemAndCheckReachableMine("Claymore", 3)
self.GiveItemAndCheckReachableMine("Obsidian Edge", 3)
self.GiveItemAndCheckReachableMine("Bone Sword", 3)
self.GiveItemAndCheckReachableMine("The Slammer", 4)
self.GiveItemAndCheckReachableMine("Lava Katana", 4)
self.GiveItemAndCheckReachableMine("Galaxy Sword", 5)
self.GiveItemAndCheckReachableMine("Galaxy Hammer", 5)
self.GiveItemAndCheckReachableMine("Galaxy Dagger", 5)
def GiveItemAndCheckReachableMine(self, item_name: str, reachable_level: int):
item = self.multiworld.create_item(item_name, self.player)
self.multiworld.state.collect(item, event=True)
if reachable_level > 0:
assert self.world.logic.can_mine_in_the_mines_floor_1_40()(self.multiworld.state)
else:
assert not self.world.logic.can_mine_in_the_mines_floor_1_40()(self.multiworld.state)
if reachable_level > 1:
assert self.world.logic.can_mine_in_the_mines_floor_41_80()(self.multiworld.state)
else:
assert not self.world.logic.can_mine_in_the_mines_floor_41_80()(self.multiworld.state)
if reachable_level > 2:
assert self.world.logic.can_mine_in_the_mines_floor_81_120()(self.multiworld.state)
else:
assert not self.world.logic.can_mine_in_the_mines_floor_81_120()(self.multiworld.state)
if reachable_level > 3:
assert self.world.logic.can_mine_in_the_skull_cavern()(self.multiworld.state)
else:
assert not self.world.logic.can_mine_in_the_skull_cavern()(self.multiworld.state)
if reachable_level > 4:
assert self.world.logic.can_mine_perfectly_in_the_skull_cavern()(self.multiworld.state)
else:
assert not self.world.logic.can_mine_perfectly_in_the_skull_cavern()(self.multiworld.state)
self.remove(item)

View File

@@ -0,0 +1,52 @@
import unittest
from .. import _True
from ..logic import _Received, _Has, _False, _And, _Or
class TestLogicSimplification(unittest.TestCase):
def test_simplify_true_in_and(self):
rules = {
"Wood": _True(),
"Rock": _True(),
}
summer = _Received("Summer", 0, 1)
assert (_Has("Wood", rules) & summer & _Has("Rock", rules)).simplify() == summer
def test_simplify_false_in_or(self):
rules = {
"Wood": _False(),
"Rock": _False(),
}
summer = _Received("Summer", 0, 1)
assert (_Has("Wood", rules) | summer | _Has("Rock", rules)).simplify() == summer
def test_simplify_and_in_and(self):
rule = _And(_And(_Received("Summer", 0, 1), _Received("Fall", 0, 1)),
_And(_Received("Winter", 0, 1), _Received("Spring", 0, 1)))
assert rule.simplify() == _And(_Received("Summer", 0, 1), _Received("Fall", 0, 1), _Received("Winter", 0, 1),
_Received("Spring", 0, 1))
def test_simplify_duplicated_and(self):
rule = _And(_And(_Received("Summer", 0, 1), _Received("Fall", 0, 1)),
_And(_Received("Summer", 0, 1), _Received("Fall", 0, 1)))
assert rule.simplify() == _And(_Received("Summer", 0, 1), _Received("Fall", 0, 1))
def test_simplify_or_in_or(self):
rule = _Or(_Or(_Received("Summer", 0, 1), _Received("Fall", 0, 1)),
_Or(_Received("Winter", 0, 1), _Received("Spring", 0, 1)))
assert rule.simplify() == _Or(_Received("Summer", 0, 1), _Received("Fall", 0, 1), _Received("Winter", 0, 1),
_Received("Spring", 0, 1))
def test_simplify_duplicated_or(self):
rule = _And(_Or(_Received("Summer", 0, 1), _Received("Fall", 0, 1)),
_Or(_Received("Summer", 0, 1), _Received("Fall", 0, 1)))
assert rule.simplify() == _Or(_Received("Summer", 0, 1), _Received("Fall", 0, 1))
def test_simplify_true_in_or(self):
rule = _Or(_True(), _Received("Summer", 0, 1))
assert rule.simplify() == _True()
def test_simplify_false_in_and(self):
rule = _And(_False(), _Received("Summer", 0, 1))
assert rule.simplify() == _False()

View File

@@ -0,0 +1,46 @@
import random
import sys
import unittest
from .. import StardewOptions, options
from ..regions import stardew_valley_regions, mandatory_connections, randomize_connections, RandomizationFlag
connections_by_name = {connection.name for connection in mandatory_connections}
regions_by_name = {region.name for region in stardew_valley_regions}
class TestRegions(unittest.TestCase):
def test_region_exits_lead_somewhere(self):
for region in stardew_valley_regions:
with self.subTest(region=region):
for exit in region.exits:
assert exit in connections_by_name, f"{region.name} is leading to {exit} but it does not exist."
def test_connection_lead_somewhere(self):
for connection in mandatory_connections:
with self.subTest(connection=connection):
assert connection.destination in regions_by_name, \
f"{connection.name} is leading to {connection.destination} but it does not exist."
class TestEntranceRando(unittest.TestCase):
def test_pelican_town_entrance_randomization(self):
for option, flag in [(options.EntranceRandomization.option_pelican_town, RandomizationFlag.PELICAN_TOWN),
(options.EntranceRandomization.option_non_progression, RandomizationFlag.NON_PROGRESSION)]:
with self.subTest(option=option, flag=flag):
seed = random.randrange(sys.maxsize)
rand = random.Random(seed)
world_options = StardewOptions({options.EntranceRandomization.internal_name: option})
_, randomized_connections = randomize_connections(rand, world_options)
for connection in mandatory_connections:
if flag in connection.flag:
assert connection.name in randomized_connections, \
f"Connection {connection.name} should be randomized but it is not in the output. Seed = {seed}"
assert connection.reverse in randomized_connections, \
f"Connection {connection.reverse} should be randomized but it is not in the output. Seed = {seed}"
assert len(set(randomized_connections.values())) == len(
randomized_connections.values()), f"Connections are duplicated in randomization. Seed = {seed}"

View File

@@ -0,0 +1,76 @@
import itertools
import math
import unittest
from BaseClasses import ItemClassification
from .. import ItemData
from ..items import Group, ResourcePackData
class TestResourcePack(unittest.TestCase):
def test_can_transform_resource_pack_data_into_idem_data(self):
resource_pack = ResourcePackData("item name", 1, 1, ItemClassification.filler, frozenset())
items = resource_pack.as_item_data(itertools.count())
assert ItemData(0, "Resource Pack: 1 item name", ItemClassification.filler, {Group.RESOURCE_PACK}) in items
assert ItemData(1, "Resource Pack: 2 item name", ItemClassification.filler, {Group.RESOURCE_PACK}) in items
assert len(items) == 2
def test_when_scale_quantity_then_generate_a_possible_quantity_from_minimal_scaling_to_double(self):
resource_pack = ResourcePackData("item name", default_amount=4, scaling_factor=2)
quantities = resource_pack.scale_quantity.items()
assert (50, 2) in quantities
assert (100, 4) in quantities
assert (150, 6) in quantities
assert (200, 8) in quantities
assert len(quantities) == (4 / 2) * 2
def test_given_scaling_not_multiple_of_default_amount_when_scale_quantity_then_double_is_added_at_200_scaling(self):
resource_pack = ResourcePackData("item name", default_amount=5, scaling_factor=3)
quantities = resource_pack.scale_quantity.items()
assert (40, 2) in quantities
assert (100, 5) in quantities
assert (160, 8) in quantities
assert (200, 10) in quantities
assert len(quantities) == math.ceil(5 / 3) * 2
def test_given_large_default_amount_multiple_of_scaling_factor_when_scale_quantity_then_scaled_amount_multiple(
self):
resource_pack = ResourcePackData("item name", default_amount=500, scaling_factor=50)
quantities = resource_pack.scale_quantity.items()
assert (10, 50) in quantities
assert (20, 100) in quantities
assert (30, 150) in quantities
assert (40, 200) in quantities
assert (50, 250) in quantities
assert (60, 300) in quantities
assert (70, 350) in quantities
assert (80, 400) in quantities
assert (90, 450) in quantities
assert (100, 500) in quantities
assert (110, 550) in quantities
assert (120, 600) in quantities
assert (130, 650) in quantities
assert (140, 700) in quantities
assert (150, 750) in quantities
assert (160, 800) in quantities
assert (170, 850) in quantities
assert (180, 900) in quantities
assert (190, 950) in quantities
assert (200, 1000) in quantities
assert len(quantities) == math.ceil(500 / 50) * 2
def test_given_smallest_multiplier_possible_when_generate_resource_pack_name_then_quantity_is_not_0(self):
resource_pack = ResourcePackData("item name", default_amount=10, scaling_factor=5)
name = resource_pack.create_name_from_multiplier(1)
assert name == "Resource Pack: 5 item name"

View File

@@ -0,0 +1,14 @@
from typing import ClassVar
from test.TestBase import WorldTestBase
from .. import StardewValleyWorld
class SVTestBase(WorldTestBase):
game = "Stardew Valley"
world: StardewValleyWorld
player: ClassVar[int] = 1
def world_setup(self, *args, **kwargs):
super().world_setup(*args, **kwargs)
self.world = self.multiworld.worlds[self.player]

View File

@@ -140,7 +140,7 @@ location_table: Dict[int, LocationDict] = {
'need_laser_cutter': False,
'position': {'x': -664.4, 'y': -97.8, 'z': -8.0}},
33029: {'can_slip_through': False,
'name': 'Grassy Plateaus West Wreck - Databox',
'name': 'Grassy Plateaus Southwest Wreck - Databox',
'need_laser_cutter': True,
'position': {'x': -421.4, 'y': -107.8, 'z': -266.5}},
33030: {'can_slip_through': False,

View File

@@ -41,7 +41,7 @@ class SubnauticaWorld(World):
location_name_to_id = all_locations
option_definitions = Options.options
data_version = 8
data_version = 9
required_client_version = (0, 3, 8)
creatures_to_scan: List[str]

View File

@@ -0,0 +1,15 @@
import unittest
from worlds import subnautica
class SubnauticaTest(unittest.TestCase):
# This is an assumption in the mod side
scancutoff: int = 33999
def testIDRange(self):
for name, id in subnautica.SubnauticaWorld.location_name_to_id.items():
with self.subTest(item=name):
if "Scan" in name:
self.assertLess(self.scancutoff, id)
else:
self.assertGreater(self.scancutoff, id)

View File

@@ -1,7 +1,7 @@
from typing import Dict, Union, List
from BaseClasses import MultiWorld
from Options import Toggle, DefaultOnToggle, DeathLink, Choice, Range, Option, OptionDict, OptionList
from schema import Schema, And, Optional
from schema import Schema, And, Optional, Or
class StartWithJewelryBox(Toggle):
@@ -308,47 +308,44 @@ class RisingTides(Toggle):
display_name = "Rising Tides"
def rising_tide_option(location: str, with_save_point_option: bool = False) -> Dict[Optional, Or]:
if with_save_point_option:
return {
Optional(location): Or(
And({
Optional("Dry"): And(int, lambda n: n >= 0),
Optional("Flooded"): And(int, lambda n: n >= 0),
Optional("FloodedWithSavePointAvailable"): And(int, lambda n: n >= 0)
}, lambda d: any(v > 0 for v in d.values())),
"Dry",
"Flooded",
"FloodedWithSavePointAvailable")
}
else:
return {
Optional(location): Or(
And({
Optional("Dry"): And(int, lambda n: n >= 0),
Optional("Flooded"): And(int, lambda n: n >= 0)
}, lambda d: any(v > 0 for v in d.values())),
"Dry",
"Flooded")
}
class RisingTidesOverrides(OptionDict):
"""Odds for specific areas to be flooded or drained, only has effect when RisingTides is on.
Areas that are not specified will roll with the default 33% chance of getting flooded or drained"""
schema = Schema({
Optional("Xarion"): {
"Dry": And(int, lambda n: n >= 0),
"Flooded": And(int, lambda n: n >= 0)
},
Optional("Maw"): {
"Dry": And(int, lambda n: n >= 0),
"Flooded": And(int, lambda n: n >= 0)
},
Optional("AncientPyramidShaft"): {
"Dry": And(int, lambda n: n >= 0),
"Flooded": And(int, lambda n: n >= 0)
},
Optional("Sandman"): {
"Dry": And(int, lambda n: n >= 0),
"Flooded": And(int, lambda n: n >= 0)
},
Optional("CastleMoat"): {
"Dry": And(int, lambda n: n >= 0),
"Flooded": And(int, lambda n: n >= 0)
},
Optional("CastleBasement"): {
"Dry": And(int, lambda n: n >= 0),
"FloodedWithSavePointAvailable": And(int, lambda n: n >= 0),
"Flooded": And(int, lambda n: n >= 0)
},
Optional("CastleCourtyard"): {
"Dry": And(int, lambda n: n >= 0),
"Flooded": And(int, lambda n: n >= 0)
},
Optional("LakeDesolation"): {
"Dry": And(int, lambda n: n >= 0),
"Flooded": And(int, lambda n: n >= 0)
},
Optional("LakeSerene"): {
"Dry": And(int, lambda n: n >= 0),
"Flooded": And(int, lambda n: n >= 0)
}
**rising_tide_option("Xarion"),
**rising_tide_option("Maw"),
**rising_tide_option("AncientPyramidShaft"),
**rising_tide_option("Sandman"),
**rising_tide_option("CastleMoat"),
**rising_tide_option("CastleBasement", with_save_point_option=True),
**rising_tide_option("CastleCourtyard"),
**rising_tide_option("LakeDesolation"),
**rising_tide_option("LakeSerene")
})
display_name = "Rising Tides Overrides"
default = {

View File

@@ -21,7 +21,7 @@ class PreCalculatedWeights:
dry_lake_serene: bool
def __init__(self, world: MultiWorld, player: int):
weights_overrrides: Dict[str, Dict[str, int]] = self.get_flood_weights_overrides(world, player)
weights_overrrides: Dict[str, Union[str, Dict[str, int]]] = self.get_flood_weights_overrides(world, player)
self.flood_basement, self.flood_basement_high = \
self.roll_flood_setting_with_available_save(world, player, weights_overrrides, "CastleBasement")
@@ -87,8 +87,8 @@ class PreCalculatedWeights:
)
@staticmethod
def get_flood_weights_overrides( world: MultiWorld, player: int) -> Dict[str, int]:
weights_overrides_option: Union[int, Dict[str, Dict[str, int]]] = \
def get_flood_weights_overrides( world: MultiWorld, player: int) -> Dict[str, Union[str, Dict[str, int]]]:
weights_overrides_option: Union[int, Dict[str, Union[str, Dict[str, int]]]] = \
get_option_value(world, player, "RisingTidesOverrides")
if weights_overrides_option == 0:
@@ -97,26 +97,32 @@ class PreCalculatedWeights:
return weights_overrides_option
@staticmethod
def roll_flood_setting(world: MultiWorld, player: int, weights: Dict[str, Dict[str, int]], key: str) -> bool:
def roll_flood_setting(world: MultiWorld, player: int, weights: Dict[str, Union[Dict[str, int], str]], key: str) -> bool:
if not world or not is_option_enabled(world, player, "RisingTides"):
return False
weights = weights[key] if key in weights else { "Dry": 67, "Flooded": 33 }
result: str = world.random.choices(list(weights.keys()), weights=list(map(int, weights.values())))[0]
if isinstance(weights, dict):
result: str = world.random.choices(list(weights.keys()), weights=list(map(int, weights.values())))[0]
else:
result: str = weights
return result == "Flooded"
@staticmethod
def roll_flood_setting_with_available_save(world: MultiWorld, player: int,
weights: Dict[str, Dict[str, int]], key: str) -> Tuple[bool, bool]:
weights: Dict[str, Union[Dict[str, int], str]], key: str) -> Tuple[bool, bool]:
if not world or not is_option_enabled(world, player, "RisingTides"):
return False, False
weights = weights[key] if key in weights else {"Dry": 66, "Flooded": 17, "FloodedWithSavePointAvailable": 17}
result: str = world.random.choices(list(weights.keys()), weights=list(map(int, weights.values())))[0]
if isinstance(weights, dict):
result: str = world.random.choices(list(weights.keys()), weights=list(map(int, weights.values())))[0]
else:
result: str = weights
if result == "Dry":
return False, False

104
worlds/wargroove/Items.py Normal file
View File

@@ -0,0 +1,104 @@
import typing
from BaseClasses import Item, ItemClassification
from typing import Dict, List
PROGRESSION = ItemClassification.progression
PROGRESSION_SKIP_BALANCING = ItemClassification.progression_skip_balancing
USEFUL = ItemClassification.useful
FILLER = ItemClassification.filler
class ItemData(typing.NamedTuple):
code: typing.Optional[int]
type: str
classification: ItemClassification = PROGRESSION
item_table: Dict[str, ItemData] = {
# Units
'Spearman': ItemData(52000, 'Unit'),
'Wagon': ItemData(52001, 'Unit', USEFUL),
'Mage': ItemData(52002, 'Unit'),
'Archer': ItemData(52003, 'Unit'),
'Knight': ItemData(52004, 'Unit'),
'Ballista': ItemData(52005, 'Unit'),
'Golem': ItemData(52006, 'Unit', USEFUL),
'Harpy': ItemData(52007, 'Unit'),
'Witch': ItemData(52008, 'Unit', USEFUL),
'Dragon': ItemData(52009, 'Unit'),
'Balloon': ItemData(52010, 'Unit', USEFUL),
'Barge': ItemData(52011, 'Unit'),
'Merfolk': ItemData(52012, 'Unit'),
'Turtle': ItemData(52013, 'Unit'),
'Harpoon Ship': ItemData(52014, 'Unit'),
'Warship': ItemData(52015, 'Unit'),
'Thief': ItemData(52016, 'Unit'),
'Rifleman': ItemData(52017, 'Unit'),
# Map Triggers
'Eastern Bridges': ItemData(52018, 'Trigger'),
'Southern Walls': ItemData(52019, 'Trigger'),
'Final Bridges': ItemData(52020, 'Trigger', PROGRESSION_SKIP_BALANCING),
'Final Walls': ItemData(52021, 'Trigger', PROGRESSION_SKIP_BALANCING),
'Final Sickle': ItemData(52022, 'Trigger', PROGRESSION_SKIP_BALANCING),
# Player Buffs
'Income Boost': ItemData(52023, 'Boost', FILLER),
'Commander Defense Boost': ItemData(52024, 'Boost', FILLER),
# Factions
'Cherrystone Commanders': ItemData(52025, 'Faction', USEFUL),
'Felheim Commanders': ItemData(52026, 'Faction', USEFUL),
'Floran Commanders': ItemData(52027, 'Faction', USEFUL),
'Heavensong Commanders': ItemData(52028, 'Faction', USEFUL),
'Requiem Commanders': ItemData(52029, 'Faction', USEFUL),
'Outlaw Commanders': ItemData(52030, 'Faction', USEFUL),
# Event Items
'Wargroove Victory': ItemData(None, 'Goal')
}
class CommanderData(typing.NamedTuple):
name: str
internal_name: str
alt_name: str = None
faction_table: Dict[str, List[CommanderData]] = {
'Starter': [
CommanderData('Mercival', 'commander_mercival')
],
'Cherrystone': [
CommanderData('Mercia', 'commander_mercia'),
CommanderData('Emeric', 'commander_emeric'),
CommanderData('Caesar', 'commander_caesar'),
],
'Felheim': [
CommanderData('Valder', 'commander_valder'),
CommanderData('Ragna', 'commander_ragna'),
CommanderData('Sigrid', 'commander_sigrid')
],
'Floran': [
CommanderData('Greenfinger', 'commander_greenfinger'),
CommanderData('Sedge', 'commander_sedge'),
CommanderData('Nuru', 'commander_nuru')
],
'Heavensong': [
CommanderData('Tenri', 'commander_tenri'),
CommanderData('Koji', 'commander_koji'),
CommanderData('Ryota', 'commander_ryota')
],
'Requiem': [
CommanderData('Elodie', 'commander_elodie'),
CommanderData('Dark Mercia', 'commander_darkmercia')
],
'Outlaw': [
CommanderData('Wulfar', 'commander_wulfar'),
CommanderData('Twins', 'commander_twins', 'Errol & Orla'),
CommanderData('Vesper', 'commander_vesper')
]
}

View File

@@ -0,0 +1,41 @@
location_table = {
'Humble Beginnings: Caesar': 53001,
'Humble Beginnings: Chest 1': 53002,
'Humble Beginnings: Chest 2': 53003,
'Humble Beginnings: Victory': 53004,
'Best Friendssss: Find Sedge': 53005,
'Best Friendssss: Victory': 53006,
'A Knight\'s Folly: Caesar': 53007,
'A Knight\'s Folly: Victory': 53008,
'Denrunaway: Chest': 53009,
'Denrunaway: Victory': 53010,
'Dragon Freeway: Victory': 53011,
'Deep Thicket: Find Sedge': 53012,
'Deep Thicket: Victory': 53013,
'Corrupted Inlet: Victory': 53014,
'Mage Mayhem: Caesar': 53015,
'Mage Mayhem: Victory': 53016,
'Endless Knight: Victory': 53017,
'Ambushed in the Middle: Victory (Blue)': 53018,
'Ambushed in the Middle: Victory (Green)': 53019,
'The Churning Sea: Victory': 53020,
'Frigid Archery: Light the Torch': 53021,
'Frigid Archery: Victory': 53022,
'Archery Lessons: Chest': 53023,
'Archery Lessons: Victory': 53024,
'Surrounded: Caesar': 53025,
'Surrounded: Victory': 53026,
'Darkest Knight: Victory': 53027,
'Robbed: Victory': 53028,
'Open Season: Caesar': 53029,
'Open Season: Victory': 53030,
'Doggo Mountain: Find all the Dogs': 53031,
'Doggo Mountain: Victory': 53032,
'Tenri\'s Fall: Victory': 53033,
'Master of the Lake: Victory': 53034,
'A Ballista\'s Revenge: Victory': 53035,
'Rebel Village: Victory (Pink)': 53036,
'Rebel Village: Victory (Red)': 53037,
'Foolish Canal: Victory': 53038,
'Wargroove Finale: Victory': None,
}

View File

@@ -0,0 +1,38 @@
import typing
from Options import Choice, Option, Range
class IncomeBoost(Range):
"""How much extra income the player gets per turn per boost received."""
display_name = "Income Boost"
range_start = 0
range_end = 100
default = 25
class CommanderDefenseBoost(Range):
"""How much extra defense the player's commander gets per boost received."""
display_name = "Commander Defense Boost"
range_start = 0
range_end = 8
default = 2
class CommanderChoice(Choice):
"""How the player's commander is selected for missions.
Locked Random: The player's commander is randomly predetermined for each level.
Unlockable Factions: The player starts with Mercival and can unlock playable factions.
Random Starting Faction: The player starts with a random starting faction and can unlock the rest.
When playing with unlockable factions, faction items are added to the pool.
Extra faction items after the first also reward starting Groove charge."""
display_name = "Commander Choice"
option_locked_random = 0
option_unlockable_factions = 1
option_random_starting_faction = 2
wargroove_options: typing.Dict[str, type(Option)] = {
"income_boost": IncomeBoost,
"commander_defense_boost": CommanderDefenseBoost,
"commander_choice": CommanderChoice
}

169
worlds/wargroove/Regions.py Normal file
View File

@@ -0,0 +1,169 @@
def create_regions(world, player: int):
from . import create_region
from .Locations import location_table
world.regions += [
create_region(world, player, 'Menu', None, ['Humble Beginnings']),
# Level 1
create_region(world, player, 'Humble Beginnings', [
'Humble Beginnings: Caesar',
'Humble Beginnings: Chest 1',
'Humble Beginnings: Chest 2',
'Humble Beginnings: Victory',
], ['Best Friendssss', 'A Knight\'s Folly', 'Denrunaway', 'Wargroove Finale']),
# Levels 2A-2C
create_region(world, player, 'Best Friendssss', [
'Best Friendssss: Find Sedge',
'Best Friendssss: Victory'
], ['Dragon Freeway', 'Deep Thicket', 'Corrupted Inlet']),
create_region(world, player, 'A Knight\'s Folly', [
'A Knight\'s Folly: Caesar',
'A Knight\'s Folly: Victory'
], ['Mage Mayhem', 'Endless Knight', 'Ambushed in the Middle']),
create_region(world, player, 'Denrunaway', [
'Denrunaway: Chest',
'Denrunaway: Victory'
], ['The Churning Sea', 'Frigid Archery', 'Archery Lessons']),
# Levels 3AA-3AC
create_region(world, player, 'Dragon Freeway', [
'Dragon Freeway: Victory',
], ['Surrounded']),
create_region(world, player, 'Deep Thicket', [
'Deep Thicket: Find Sedge',
'Deep Thicket: Victory',
], ['Darkest Knight']),
create_region(world, player, 'Corrupted Inlet', [
'Corrupted Inlet: Victory',
], ['Robbed']),
# Levels 3BA-3BC
create_region(world, player, 'Mage Mayhem', [
'Mage Mayhem: Caesar',
'Mage Mayhem: Victory',
], ['Open Season', 'Foolish Canal: Mage Mayhem Entrance']),
create_region(world, player, 'Endless Knight', [
'Endless Knight: Victory',
], ['Doggo Mountain', 'Foolish Canal: Endless Knight Entrance']),
create_region(world, player, 'Ambushed in the Middle', [
'Ambushed in the Middle: Victory (Blue)',
'Ambushed in the Middle: Victory (Green)',
], ['Tenri\'s Fall']),
# Levels 3CA-3CC
create_region(world, player, 'The Churning Sea', [
'The Churning Sea: Victory',
], ['Rebel Village']),
create_region(world, player, 'Frigid Archery', [
'Frigid Archery: Light the Torch',
'Frigid Archery: Victory',
], ['A Ballista\'s Revenge']),
create_region(world, player, 'Archery Lessons', [
'Archery Lessons: Chest',
'Archery Lessons: Victory',
], ['Master of the Lake']),
# Levels 4AA-4AC
create_region(world, player, 'Surrounded', [
'Surrounded: Caesar',
'Surrounded: Victory',
]),
create_region(world, player, 'Darkest Knight', [
'Darkest Knight: Victory',
]),
create_region(world, player, 'Robbed', [
'Robbed: Victory',
]),
# Levels 4BAA-4BCA
create_region(world, player, 'Open Season', [
'Open Season: Caesar',
'Open Season: Victory',
]),
create_region(world, player, 'Doggo Mountain', [
'Doggo Mountain: Find all the Dogs',
'Doggo Mountain: Victory',
]),
create_region(world, player, 'Tenri\'s Fall', [
'Tenri\'s Fall: Victory',
]),
# Level 4BAB
create_region(world, player, 'Foolish Canal', [
'Foolish Canal: Victory',
]),
# Levels 4CA-4CC
create_region(world, player, 'Master of the Lake', [
'Master of the Lake: Victory',
]),
create_region(world, player, 'A Ballista\'s Revenge', [
'A Ballista\'s Revenge: Victory',
]),
create_region(world, player, 'Rebel Village', [
'Rebel Village: Victory (Pink)',
'Rebel Village: Victory (Red)',
]),
# Final Level
create_region(world, player, 'Wargroove Finale', [
'Wargroove Finale: Victory'
]),
]
# link up our regions with the entrances
world.get_entrance('Humble Beginnings', player).connect(world.get_region('Humble Beginnings', player))
world.get_entrance('Best Friendssss', player).connect(world.get_region('Best Friendssss', player))
world.get_entrance('A Knight\'s Folly', player).connect(world.get_region('A Knight\'s Folly', player))
world.get_entrance('Denrunaway', player).connect(world.get_region('Denrunaway', player))
world.get_entrance('Wargroove Finale', player).connect(world.get_region('Wargroove Finale', player))
world.get_entrance('Dragon Freeway', player).connect(world.get_region('Dragon Freeway', player))
world.get_entrance('Deep Thicket', player).connect(world.get_region('Deep Thicket', player))
world.get_entrance('Corrupted Inlet', player).connect(world.get_region('Corrupted Inlet', player))
world.get_entrance('Mage Mayhem', player).connect(world.get_region('Mage Mayhem', player))
world.get_entrance('Endless Knight', player).connect(world.get_region('Endless Knight', player))
world.get_entrance('Ambushed in the Middle', player).connect(world.get_region('Ambushed in the Middle', player))
world.get_entrance('The Churning Sea', player).connect(world.get_region('The Churning Sea', player))
world.get_entrance('Frigid Archery', player).connect(world.get_region('Frigid Archery', player))
world.get_entrance('Archery Lessons', player).connect(world.get_region('Archery Lessons', player))
world.get_entrance('Surrounded', player).connect(world.get_region('Surrounded', player))
world.get_entrance('Darkest Knight', player).connect(world.get_region('Darkest Knight', player))
world.get_entrance('Robbed', player).connect(world.get_region('Robbed', player))
world.get_entrance('Open Season', player).connect(world.get_region('Open Season', player))
world.get_entrance('Doggo Mountain', player).connect(world.get_region('Doggo Mountain', player))
world.get_entrance('Tenri\'s Fall', player).connect(world.get_region('Tenri\'s Fall', player))
world.get_entrance('Foolish Canal: Mage Mayhem Entrance', player).connect(world.get_region('Foolish Canal', player))
world.get_entrance('Foolish Canal: Endless Knight Entrance', player).connect(
world.get_region('Foolish Canal', player)
)
world.get_entrance('Master of the Lake', player).connect(world.get_region('Master of the Lake', player))
world.get_entrance('A Ballista\'s Revenge', player).connect(world.get_region('A Ballista\'s Revenge', player))
world.get_entrance('Rebel Village', player).connect(world.get_region('Rebel Village', player))

161
worlds/wargroove/Rules.py Normal file
View File

@@ -0,0 +1,161 @@
from typing import List
from BaseClasses import MultiWorld, Region, Location
from ..AutoWorld import LogicMixin
from ..generic.Rules import set_rule
class WargrooveLogic(LogicMixin):
def _wargroove_has_item(self, player: int, item: str) -> bool:
return self.has(item, player)
def _wargroove_has_region(self, player: int, region: str) -> bool:
return self.can_reach(region, 'Region', player)
def _wargroove_has_item_and_region(self, player: int, item: str, region: str) -> bool:
return self.can_reach(region, 'Region', player) and self.has(item, player)
def set_rules(world: MultiWorld, player: int):
# Final Level
set_rule(world.get_location('Wargroove Finale: Victory', player),
lambda state: state._wargroove_has_item(player, "Final Bridges") and
state._wargroove_has_item(player, "Final Walls") and
state._wargroove_has_item(player, "Final Sickle"))
# Level 1
set_rule(world.get_location('Humble Beginnings: Caesar', player), lambda state: True)
set_rule(world.get_location('Humble Beginnings: Chest 1', player), lambda state: True)
set_rule(world.get_location('Humble Beginnings: Chest 2', player), lambda state: True)
set_rule(world.get_location('Humble Beginnings: Victory', player), lambda state: True)
set_region_exit_rules(world.get_region('Humble Beginnings', player),
[world.get_location('Humble Beginnings: Victory', player)])
# Levels 2A-2C
set_rule(world.get_location('Best Friendssss: Find Sedge', player), lambda state: True)
set_rule(world.get_location('Best Friendssss: Victory', player), lambda state: True)
set_region_exit_rules(world.get_region('Best Friendssss', player),
[world.get_location('Best Friendssss: Victory', player)])
set_rule(world.get_location('A Knight\'s Folly: Caesar', player), lambda state: True)
set_rule(world.get_location('A Knight\'s Folly: Victory', player), lambda state: True)
set_region_exit_rules(world.get_region('A Knight\'s Folly', player),
[world.get_location('A Knight\'s Folly: Victory', player)])
set_rule(world.get_location('Denrunaway: Chest', player), lambda state: True)
set_rule(world.get_location('Denrunaway: Victory', player), lambda state: True)
set_region_exit_rules(world.get_region('Denrunaway', player), [world.get_location('Denrunaway: Victory', player)])
# Levels 3AA-3AC
set_rule(world.get_location('Dragon Freeway: Victory', player),
lambda state: state._wargroove_has_item(player, 'Mage'))
set_region_exit_rules(world.get_region('Dragon Freeway', player),
[world.get_location('Dragon Freeway: Victory', player)])
set_rule(world.get_location('Deep Thicket: Find Sedge', player),
lambda state: state._wargroove_has_item(player, 'Mage'))
set_rule(world.get_location('Deep Thicket: Victory', player),
lambda state: state._wargroove_has_item(player, 'Mage'))
set_region_exit_rules(world.get_region('Deep Thicket', player),
[world.get_location('Deep Thicket: Victory', player)])
set_rule(world.get_location('Corrupted Inlet: Victory', player),
lambda state: state._wargroove_has_item(player, 'Barge') or
state._wargroove_has_item(player, 'Merfolk') or
state._wargroove_has_item(player, 'Warship'))
set_region_exit_rules(world.get_region('Corrupted Inlet', player),
[world.get_location('Corrupted Inlet: Victory', player)])
# Levels 3BA-3BC
set_rule(world.get_location('Mage Mayhem: Caesar', player),
lambda state: state._wargroove_has_item(player, 'Harpy') or state._wargroove_has_item(player, 'Dragon'))
set_rule(world.get_location('Mage Mayhem: Victory', player),
lambda state: state._wargroove_has_item(player, 'Harpy') or state._wargroove_has_item(player, 'Dragon'))
set_region_exit_rules(world.get_region('Mage Mayhem', player), [world.get_location('Mage Mayhem: Victory', player)])
set_rule(world.get_location('Endless Knight: Victory', player),
lambda state: state._wargroove_has_item(player, 'Eastern Bridges') and (
state._wargroove_has_item(player, 'Spearman') or
state._wargroove_has_item(player, 'Harpy') or
state._wargroove_has_item(player, 'Dragon')))
set_region_exit_rules(world.get_region('Endless Knight', player),
[world.get_location('Endless Knight: Victory', player)])
set_rule(world.get_location('Ambushed in the Middle: Victory (Blue)', player),
lambda state: state._wargroove_has_item(player, 'Spearman'))
set_rule(world.get_location('Ambushed in the Middle: Victory (Green)', player),
lambda state: state._wargroove_has_item(player, 'Spearman'))
set_region_exit_rules(world.get_region('Ambushed in the Middle', player),
[world.get_location('Ambushed in the Middle: Victory (Blue)', player),
world.get_location('Ambushed in the Middle: Victory (Green)', player)])
# Levels 3CA-3CC
set_rule(world.get_location('The Churning Sea: Victory', player),
lambda state: (state._wargroove_has_item(player, 'Merfolk') or state._wargroove_has_item(player, 'Turtle'))
and state._wargroove_has_item(player, 'Harpoon Ship'))
set_region_exit_rules(world.get_region('The Churning Sea', player),
[world.get_location('The Churning Sea: Victory', player)])
set_rule(world.get_location('Frigid Archery: Light the Torch', player),
lambda state: state._wargroove_has_item(player, 'Archer') and
state._wargroove_has_item(player, 'Southern Walls'))
set_rule(world.get_location('Frigid Archery: Victory', player),
lambda state: state._wargroove_has_item(player, 'Archer'))
set_region_exit_rules(world.get_region('Frigid Archery', player),
[world.get_location('Frigid Archery: Victory', player)])
set_rule(world.get_location('Archery Lessons: Chest', player),
lambda state: state._wargroove_has_item(player, 'Knight') and
state._wargroove_has_item(player, 'Southern Walls'))
set_rule(world.get_location('Archery Lessons: Victory', player),
lambda state: state._wargroove_has_item(player, 'Knight') and
state._wargroove_has_item(player, 'Southern Walls'))
set_region_exit_rules(world.get_region('Archery Lessons', player),
[world.get_location('Archery Lessons: Victory', player)])
# Levels 4AA-4AC
set_rule(world.get_location('Surrounded: Caesar', player),
lambda state: state._wargroove_has_item_and_region(player, 'Knight', 'Surrounded'))
set_rule(world.get_location('Surrounded: Victory', player),
lambda state: state._wargroove_has_item_and_region(player, 'Knight', 'Surrounded'))
set_rule(world.get_location('Darkest Knight: Victory', player),
lambda state: state._wargroove_has_item_and_region(player, 'Spearman', 'Darkest Knight'))
set_rule(world.get_location('Robbed: Victory', player),
lambda state: state._wargroove_has_item_and_region(player, 'Thief', 'Robbed') and
state._wargroove_has_item(player, 'Rifleman'))
# Levels 4BA-4BC
set_rule(world.get_location('Open Season: Caesar', player),
lambda state: state._wargroove_has_item_and_region(player, 'Mage', 'Open Season') and
state._wargroove_has_item(player, 'Knight'))
set_rule(world.get_location('Open Season: Victory', player),
lambda state: state._wargroove_has_item_and_region(player, 'Mage', 'Open Season') and
state._wargroove_has_item(player, 'Knight'))
set_rule(world.get_location('Doggo Mountain: Find all the Dogs', player),
lambda state: state._wargroove_has_item_and_region(player, 'Knight', 'Doggo Mountain'))
set_rule(world.get_location('Doggo Mountain: Victory', player),
lambda state: state._wargroove_has_item_and_region(player, 'Knight', 'Doggo Mountain'))
set_rule(world.get_location('Tenri\'s Fall: Victory', player),
lambda state: state._wargroove_has_item_and_region(player, 'Mage', 'Tenri\'s Fall') and
state._wargroove_has_item(player, 'Thief'))
set_rule(world.get_location('Foolish Canal: Victory', player),
lambda state: state._wargroove_has_item_and_region(player, 'Mage', 'Foolish Canal') and
state._wargroove_has_item(player, 'Spearman'))
# Levels 4CA-4CC
set_rule(world.get_location('Master of the Lake: Victory', player),
lambda state: state._wargroove_has_item_and_region(player, 'Warship', 'Master of the Lake'))
set_rule(world.get_location('A Ballista\'s Revenge: Victory', player),
lambda state: state._wargroove_has_item_and_region(player, 'Ballista', 'A Ballista\'s Revenge'))
set_rule(world.get_location('Rebel Village: Victory (Pink)', player),
lambda state: state._wargroove_has_item_and_region(player, 'Spearman', 'Rebel Village'))
set_rule(world.get_location('Rebel Village: Victory (Red)', player),
lambda state: state._wargroove_has_item_and_region(player, 'Spearman', 'Rebel Village'))
def set_region_exit_rules(region: Region, locations: List[Location], operator: str = "or"):
if operator == "or":
exit_rule = lambda state: any(location.access_rule(state) for location in locations)
else:
exit_rule = lambda state: all(location.access_rule(state) for location in locations)
for region_exit in region.exits:
region_exit.access_rule = exit_rule

View File

@@ -0,0 +1,28 @@
<FactionBox>:
orientation: 'vertical'
padding: [10,5,10,5]
size_hint_y: 0.14
<CommanderGroup>:
orientation: 'horizontal'
<CommanderButton>:
text_size: self.size
size_hint: (None, 0.8)
width: 100
markup: True
halign: 'center'
valign: 'middle'
padding_x: 5
outline_width: 1
disabled: True
on_release: setattr(self, 'state', 'down')
<ItemTracker>:
orientation: 'horizontal'
padding_y: 5
<ItemLabel>:
size_hint_x: None
size: self.texture_size
pos_hint: {'left': 1}

View File

@@ -0,0 +1,139 @@
import os
import string
import json
from BaseClasses import Item, MultiWorld, Region, Location, Entrance, Tutorial, ItemClassification
from .Items import item_table, faction_table
from .Locations import location_table
from .Regions import create_regions
from .Rules import set_rules
from ..AutoWorld import World, WebWorld
from .Options import wargroove_options
class WargrooveWeb(WebWorld):
tutorials = [Tutorial(
"Multiworld Setup Guide",
"A guide to setting up Wargroove for Archipelago.",
"English",
"wargroove_en.md",
"wargroove/en",
["Fly Sniper"]
)]
class WargrooveWorld(World):
"""
Command an army, in this retro style turn based strategy game!
"""
option_definitions = wargroove_options
game = "Wargroove"
topology_present = True
data_version = 1
web = WargrooveWeb()
item_name_to_id = {name: data.code for name, data in item_table.items()}
location_name_to_id = location_table
def _get_slot_data(self):
return {
'seed': "".join(self.multiworld.per_slot_randoms[self.player].choice(string.ascii_letters) for i in range(16)),
'income_boost': self.multiworld.income_boost[self.player],
'commander_defense_boost': self.multiworld.commander_defense_boost[self.player],
'can_choose_commander': self.multiworld.commander_choice[self.player] != 0,
'starting_groove_multiplier': 20 # Backwards compatibility in case this ever becomes an option
}
def generate_early(self):
# Selecting a random starting faction
if self.multiworld.commander_choice[self.player] == 2:
factions = [faction for faction in faction_table.keys() if faction != "Starter"]
starting_faction = WargrooveItem(self.multiworld.random.choice(factions) + ' Commanders', self.player)
self.multiworld.push_precollected(starting_faction)
def generate_basic(self):
# Fill out our pool with our items from the item table
pool = []
precollected_item_names = {item.name for item in self.multiworld.precollected_items[self.player]}
ignore_faction_items = self.multiworld.commander_choice[self.player] == 0
for name, data in item_table.items():
if data.code is not None and name not in precollected_item_names and not data.classification == ItemClassification.filler:
if name.endswith(' Commanders') and ignore_faction_items:
continue
item = WargrooveItem(name, self.player)
pool.append(item)
# Matching number of unfilled locations with filler items
locations_remaining = len(location_table) - 1 - len(pool)
while locations_remaining > 0:
# Filling the pool equally with both types of filler items
pool.append(WargrooveItem("Commander Defense Boost", self.player))
locations_remaining -= 1
if locations_remaining > 0:
pool.append(WargrooveItem("Income Boost", self.player))
locations_remaining -= 1
self.multiworld.itempool += pool
# Placing victory event at final location
victory = WargrooveItem("Wargroove Victory", self.player)
self.multiworld.get_location("Wargroove Finale: Victory", self.player).place_locked_item(victory)
self.multiworld.completion_condition[self.player] = lambda state: state.has("Wargroove Victory", self.player)
def set_rules(self):
set_rules(self.multiworld, self.player)
def create_item(self, name: str) -> Item:
return WargrooveItem(name, self.player)
def create_regions(self):
create_regions(self.multiworld, self.player)
def fill_slot_data(self) -> dict:
slot_data = self._get_slot_data()
for option_name in wargroove_options:
option = getattr(self.multiworld, option_name)[self.player]
slot_data[option_name] = int(option.value)
return slot_data
def get_filler_item_name(self) -> str:
return self.multiworld.random.choice(["Commander Defense Boost", "Income Boost"])
def create_region(world: MultiWorld, player: int, name: str, locations=None, exits=None):
ret = Region(name, player, world)
if locations:
for location in locations:
loc_id = location_table.get(location, 0)
location = WargrooveLocation(player, location, loc_id, ret)
ret.locations.append(location)
if exits:
for exit in exits:
ret.exits.append(Entrance(player, exit, ret))
return ret
class WargrooveLocation(Location):
game: str = "Wargroove"
def __init__(self, player: int, name: str, address=None, parent=None):
super(WargrooveLocation, self).__init__(player, name, address, parent)
if address is None:
self.event = True
self.locked = True
class WargrooveItem(Item):
game = "Wargroove"
def __init__(self, name, player: int = None):
item_data = item_table[name]
super(WargrooveItem, self).__init__(
name,
item_data.classification,
item_data.code,
player
)

Binary file not shown.

Binary file not shown.

View File

@@ -0,0 +1,34 @@
# Wargroove (Steam, Windows)
## Where is the settings page?
The [player settings page for this game](../player-settings) contains all the options you need to configure and export a
config file.
## What does randomization do to this game?
This randomizer shuffles units, map events, factions and boosts. It features a custom, non-linear campaign where the
final level and 3 branching paths are all available to the player from the start. The player cannot beat the final level
without specific items scattered throughout the branching paths. Certain levels on these paths may require
specific units or items in order to progress.
## What items and locations get shuffled?
1. Every buildable unit in the game (except for soldiers and dogs, which are free).
2. Commanders available to certain factions. If the player acquires the Floran Commanders, they can select any commander
from that faction.
3. Income and Commander Defense boosts that provide the player with extra income or extra commander defense.
4. Special map events like the Eastern Bridges or the Southern Walls, which unlock certain locations in certain levels.
## Which items can be in another player's world?
Any of the above items can be in another player's world.
## When the player receives an item, what happens?
When the player receives an item, a message will appear in Wargroove with the item name and sender name, once an action
is taken in game.
## What is the goal of this game when randomized?
The goal is to beat the level titled `The End` by finding the `Final Bridges`, `Final Walls`, and `Final Sickle`.

View File

@@ -0,0 +1,83 @@
# Wargroove Setup Guide
## Required Files
- Wargroove with the Double Trouble DLC installed through Steam on Windows
- Only the Steam Windows version is supported. MAC, Switch, Xbox, and Playstation are not supported.
- [The most recent Archipelago release](https://github.com/ArchipelagoMW/Archipelago/releases)
## Backup playerProgress files
`playerProgress` and `playerProgress.bak` contain save data for all of your Wargroove campaigns. Backing up these files
is strongly recommended in case they become corrupted.
1. Type `%appdata%\Chucklefish\Wargroove\save` in the file browser and hit enter.
2. Copy the `playerProgress` and `playerProgress.bak` files and paste them into a backup directory.
## Update host.yaml to include the Wargroove root directory
1. Look for your Archipelago install files. By default, the installer puts them in `C:\ProgramData\Archipelago`.
2. Open the `host.yaml` file in your favorite text editor (Notepad will work).
3. Put your Wargroove root directory in the `root_directory:` under the `wargroove_options:` section.
- The Wargroove root directory can be found by going to
`Steam->Right Click Wargroove->Properties->Local Files->Browse Local Files` and copying the path in the address bar.
- Paste the path in between the quotes next to `root_directory:` in the `host.yaml`.
- You may have to replace all single \\ with \\\\.
4. Start the Wargroove client.
## Installing the Archipelago Wargroove Mod and Campaign files
1. Shut down Wargroove if it is open.
2. Start the ArchipelagoWargrooveClient.exe from the Archipelago installation.
This should install the mod and campaign for you.
3. Start Wargroove.
## Verify the campaign can be loaded
1. Start Wargroove from Steam.
2. Go to `Story->Campaign->Custom->Archipelago` and click play. You should see the first level.
## Starting a Multiworld game
1. Start the Wargroove Client and connect to the server. Enter your username from your
[settings file.](/games/Wargroove/player-settings)
2. Start Wargroove and play the Archipelago campaign by going to `Story->Campaign->Custom->Archipelago`.
## Ending a Multiworld game
It is strongly recommended that you delete your campaign progress after finishing a multiworld game.
This can be done by going to the level selection screen in the Archipelago campaign, hitting `ESC` and clicking the
`Delete Progress` button. The main menu should now be visible.
## Updating to a new version of the Wargroove mod or downloading new campaign files
First, delete your campaign progress by going to the level selection screen in the Archipelago campaign,
hitting `ESC` and clicking the `Delete Progress` button.
Follow the `Installing the Archipelago Wargroove Mod and Campaign files` steps again, but look for the latest version
to download. In addition, follow the steps outlined in `Wargroove crashes when trying to run the Archipelago campaign`
when attempting to update the campaign files and the mod.
## Troubleshooting
### The game is too hard
`Go to the campaign overview screen->Hit escape on the keyboard->Click adjust difficulty->Adjust the setttings`
### The mod doesn't load
Double-check the mod installation under `%appdata%\Chucklefish\Wargroove\mods`. There should be 3 `.dat` files in
`%appdata%\Chucklefish\Wargroove\mods\ArchipelagoMod`. Otherwise, follow
`Installing the Archipelago Wargroove Mod and Campaign files` steps once more.
### Wargroove crashes or there is a lua error
Wargroove is finicky, but there could be several causes for this. If it happens often or can be reproduced,
please submit a bug report in the tech-support channel on the [discord](https://discord.gg/archipelago).
### Wargroove crashes when trying to run the Archipelago campaign
This is caused by not deleting campaign progress before updating the mod and campaign files.
1. Go to `Custom Content->Create->Campaign->Archipelago->Edit` and attempt to update the mod.
2. Wargroove will give an error message.
3. Go back to `Custom Content->Create->Campaign->Archipelago->Edit` and attempt to update the mod again.
4. Wargroove crashes.
5. Go back to `Custom Content->Create->Campaign->Archipelago->Edit` and attempt to update the mod again.
6. In the edit menu, hit `ESC` and click `Delete Progress`.
7. If the above steps do not allow you to start the campaign from `Story->Campaign->Custom->Archipelago` replace
`playerProgress` and `playerProgress.bak` with your previously backed up files.
### Mod is out of date when trying to run the Archipelago campaign
Please follow the above steps in `Wargroove crashes when trying to run the Archipelago campaign`.