Merge remote-tracking branch 'origin/OverworldShuffle' into OverworldShuffle
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
import random
|
||||
from Utils import int16_as_bytes
|
||||
from Utils import int16_as_bytes, snes_to_pc
|
||||
|
||||
|
||||
class SFX(object):
|
||||
@@ -126,7 +126,7 @@ def shuffle_sfx_data():
|
||||
random.shuffle(candidates)
|
||||
|
||||
# place chained sfx first
|
||||
random.shuffle(chained_sfx) # todo: sort largest to smallest
|
||||
random.shuffle(chained_sfx)
|
||||
chained_sfx = sorted(chained_sfx, key=lambda x: len(x.chain), reverse=True)
|
||||
for chained in chained_sfx:
|
||||
chosen_slot = next(x for x in candidates if len(accompaniment_map[x[0]]) - len(chained.chain) >= 0)
|
||||
@@ -153,16 +153,13 @@ def shuffle_sfx_data():
|
||||
|
||||
|
||||
sfx_table = {
|
||||
2: 0x1a8c29,
|
||||
3: 0x1A8D25
|
||||
2: 0x1A8BD0,
|
||||
3: 0x1A8CCC
|
||||
}
|
||||
|
||||
# 0x1a8c29
|
||||
# d8059
|
||||
|
||||
sfx_accompaniment_table = {
|
||||
2: 0x1A8CA7,
|
||||
3: 0x1A8DA3
|
||||
2: 0x1A8C4E,
|
||||
3: 0x1A8D4A
|
||||
}
|
||||
|
||||
|
||||
@@ -171,9 +168,9 @@ def randomize_sfx(rom):
|
||||
|
||||
for shuffled_sfx in sfx_map.values():
|
||||
for sfx in shuffled_sfx.values():
|
||||
base_address = sfx_table[sfx.target_set]
|
||||
base_address = snes_to_pc(sfx_table[sfx.target_set])
|
||||
rom.write_bytes(base_address + (sfx.target_id * 2) - 2, int16_as_bytes(sfx.addr))
|
||||
ac_base = sfx_accompaniment_table[sfx.target_set]
|
||||
ac_base = snes_to_pc(sfx_accompaniment_table[sfx.target_set])
|
||||
last = sfx.target_id
|
||||
if sfx.target_chain:
|
||||
for chained in sfx.target_chain:
|
||||
|
||||
@@ -4,6 +4,7 @@ import json
|
||||
import os
|
||||
import random
|
||||
import shutil
|
||||
import ssl
|
||||
from urllib.parse import urlparse
|
||||
from urllib.request import urlopen
|
||||
import webbrowser
|
||||
@@ -149,7 +150,7 @@ class SpriteSelector(object):
|
||||
|
||||
try:
|
||||
task.update_status("Downloading official sprites list")
|
||||
with urlopen('https://alttpr.com/sprites') as response:
|
||||
with urlopen('https://alttpr.com/sprites', context=ssl._create_unverified_context()) as response:
|
||||
sprites_arr = json.loads(response.read().decode("utf-8"))
|
||||
except Exception as e:
|
||||
resultmessage = "Error getting list of official sprites. Sprites not updated.\n\n%s: %s" % (type(e).__name__, e)
|
||||
|
||||
@@ -73,7 +73,8 @@ SETTINGSTOPROCESS = {
|
||||
"progressives": "progressive",
|
||||
"accessibility": "accessibility",
|
||||
"sortingalgo": "algorithm",
|
||||
"beemizer": "beemizer"
|
||||
"beemizer": "beemizer",
|
||||
"restrict_boss_items": "restrict_boss_items"
|
||||
},
|
||||
"overworld": {
|
||||
"overworldshuffle": "ow_shuffle",
|
||||
@@ -81,13 +82,15 @@ SETTINGSTOPROCESS = {
|
||||
"keepsimilar": "ow_keepsimilar",
|
||||
"mixed": "ow_mixed",
|
||||
"whirlpool": "ow_whirlpool",
|
||||
"bonk_drops": "bonk_drops",
|
||||
"overworldflute": "ow_fluteshuffle"
|
||||
},
|
||||
"entrance": {
|
||||
"openpyramid": "openpyramid",
|
||||
"shuffleganon": "shuffleganon",
|
||||
"shufflelinks": "shufflelinks",
|
||||
"entranceshuffle": "shuffle"
|
||||
"entranceshuffle": "shuffle",
|
||||
"overworld_map": "overworld_map",
|
||||
},
|
||||
"enemizer": {
|
||||
"enemyshuffle": "shuffleenemies",
|
||||
@@ -100,9 +103,12 @@ SETTINGSTOPROCESS = {
|
||||
"compassshuffle": "compassshuffle",
|
||||
"smallkeyshuffle": "keyshuffle",
|
||||
"bigkeyshuffle": "bigkeyshuffle",
|
||||
"keydropshuffle": "keydropshuffle",
|
||||
"dungeondoorshuffle": "door_shuffle",
|
||||
"dungeonintensity": "intensity",
|
||||
"keydropshuffle": "keydropshuffle",
|
||||
"dropshuffle": "dropshuffle",
|
||||
"pottery": "pottery",
|
||||
"colorizepots": "colorizepots",
|
||||
"potshuffle": "shufflepots",
|
||||
"experimental": "experimental",
|
||||
"dungeon_counters": "dungeon_counters",
|
||||
@@ -118,9 +124,12 @@ SETTINGSTOPROCESS = {
|
||||
"owpalettes": "ow_palettes",
|
||||
"uwpalettes": "uw_palettes",
|
||||
"reduce_flashing": "reduce_flashing",
|
||||
"shuffle_sfx": "shuffle_sfx"
|
||||
"shuffle_sfx": "shuffle_sfx",
|
||||
'msu_resume': 'msu_resume',
|
||||
'collection_rate': 'collection_rate',
|
||||
},
|
||||
"generation": {
|
||||
"bps": "bps",
|
||||
"createspoiler": "create_spoiler",
|
||||
"createrom": "create_rom",
|
||||
"calcplaythrough": "calc_playthrough",
|
||||
|
||||
58
source/dungeon/RoomList.py
Normal file
58
source/dungeon/RoomList.py
Normal file
@@ -0,0 +1,58 @@
|
||||
from RoomData import DoorKind, Position
|
||||
from source.dungeon.RoomObject import RoomObject, DoorObject
|
||||
|
||||
|
||||
class Room:
|
||||
|
||||
def __init__(self, layout, layer1, layer2, doors):
|
||||
self.layout = layout
|
||||
self.layer1 = layer1
|
||||
self.layer2 = layer2
|
||||
self.doors = doors
|
||||
|
||||
def write_to_rom(self, address, rom):
|
||||
rom.write_bytes(address, self.layout)
|
||||
address += 2
|
||||
for obj in self.layer1:
|
||||
rom.write_bytes(address, obj.data)
|
||||
address += 3
|
||||
rom.write_bytes(address, [0xFF, 0xFF])
|
||||
address += 2
|
||||
for obj in self.layer2:
|
||||
rom.write_bytes(address, obj.data)
|
||||
address += 3
|
||||
rom.write_bytes(address, [0xFF, 0xFF, 0xF0, 0xFF])
|
||||
address += 4
|
||||
for door in self.doors:
|
||||
rom.write_bytes(address, door.get_bytes())
|
||||
address += 2
|
||||
rom.write_bytes(address, [0xFF, 0xFF])
|
||||
return address + 2 # where the data ended
|
||||
|
||||
|
||||
Room0127 = Room([0xE1, 0x00],
|
||||
[RoomObject(0x0AB600, [0xFE, 0x89, 0x00]),
|
||||
RoomObject(0x0AB603, [0xA2, 0xA1, 0x61]),
|
||||
RoomObject(0x0AB606, [0xFE, 0x8E, 0x81]),
|
||||
RoomObject(0x0AB609, [0xFF, 0x49, 0x02]),
|
||||
RoomObject(0x0AB60C, [0xD2, 0xA1, 0x62]),
|
||||
RoomObject(0x0AB60F, [0xFF, 0x4E, 0x83]),
|
||||
RoomObject(0x0AB612, [0x20, 0xB3, 0xDD]),
|
||||
RoomObject(0x0AB615, [0x50, 0xB3, 0xDD]),
|
||||
RoomObject(0x0AB618, [0x33, 0xCB, 0xFA]),
|
||||
RoomObject(0x0AB61B, [0x3B, 0xCB, 0xFA]),
|
||||
RoomObject(0x0AB61E, [0x43, 0xCB, 0xFA]),
|
||||
RoomObject(0x0AB621, [0x4B, 0xCB, 0xFA]),
|
||||
RoomObject(0x0AB624, [0xBF, 0x94, 0xF9]),
|
||||
RoomObject(0x0AB627, [0xB3, 0xB3, 0xFA]),
|
||||
RoomObject(0x0AB62A, [0xCB, 0xB3, 0xFA]),
|
||||
RoomObject(0x0AB62D, [0xAD, 0xC8, 0xDF]),
|
||||
RoomObject(0x0AB630, [0xC4, 0xC8, 0xDF]),
|
||||
RoomObject(0x0AB633, [0xB3, 0xE3, 0xFA]),
|
||||
RoomObject(0x0AB636, [0xCB, 0xE3, 0xFA]),
|
||||
RoomObject(0x0AB639, [0x81, 0x93, 0xC0]),
|
||||
RoomObject(0x0AB63C, [0x81, 0xD2, 0xC0]),
|
||||
RoomObject(0x0AB63F, [0xE1, 0x93, 0xC0]),
|
||||
RoomObject(0x0AB642, [0xE1, 0xD2, 0xC0])],
|
||||
[], [DoorObject(Position.SouthW, DoorKind.CaveEntrance),
|
||||
DoorObject(Position.SouthE, DoorKind.CaveEntrance)])
|
||||
34
source/dungeon/RoomObject.py
Normal file
34
source/dungeon/RoomObject.py
Normal file
@@ -0,0 +1,34 @@
|
||||
from Utils import snes_to_pc
|
||||
|
||||
# Subtype 3 object (0x2xx by jpdasm id - see bank 01)
|
||||
# B
|
||||
Normal_Pot = (0xFA, 3, 3)
|
||||
Shuffled_Pot = (0xFB, 0, 0) # formerly weird pot, or black diagonal thing
|
||||
|
||||
|
||||
class RoomObject:
|
||||
|
||||
def __init__(self, address, data):
|
||||
self.address = address
|
||||
self.data = data
|
||||
|
||||
def change_type(self, new_type):
|
||||
type_id, datum_a, datum_b = new_type
|
||||
if 0xF8 <= type_id < 0xFC: # sub type 3
|
||||
self.data = (self.data[0] & 0xFC) | datum_a, (self.data[1] & 0xFC) | datum_b, type_id
|
||||
else:
|
||||
pass # not yet implemented
|
||||
|
||||
def write_to_rom(self, rom):
|
||||
rom.write_bytes(snes_to_pc(self.address), self.data)
|
||||
|
||||
|
||||
class DoorObject:
|
||||
|
||||
def __init__(self, pos, kind):
|
||||
self.pos = pos
|
||||
self.kind = kind
|
||||
|
||||
def get_bytes(self):
|
||||
return [self.pos.value, self.kind.value]
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
from tkinter import ttk, filedialog, messagebox, StringVar, Button, Entry, Frame, Label, E, W, LEFT, RIGHT, X, BOTTOM
|
||||
from AdjusterMain import adjust
|
||||
from AdjusterMain import adjust, patch
|
||||
from argparse import Namespace
|
||||
from source.classes.SpriteSelector import SpriteSelector
|
||||
import source.gui.widgets as widgets
|
||||
@@ -79,7 +79,9 @@ def adjust_page(top, parent, settings):
|
||||
romEntry2 = Entry(adjustRomFrame, textvariable=self.romVar2)
|
||||
|
||||
def RomSelect2():
|
||||
rom = filedialog.askopenfilename(filetypes=[("Rom Files", (".sfc", ".smc")), ("All Files", "*")])
|
||||
initdir = os.path.join(os.getcwd(), settings['outputpath']) if 'outputpath' in settings else os.getcwd()
|
||||
rom = filedialog.askopenfilename(filetypes=[("Rom Files", (".sfc", ".smc")), ("All Files", "*")],
|
||||
initialdir=initdir)
|
||||
if rom:
|
||||
settings["rom"] = rom
|
||||
self.romVar2.set(rom)
|
||||
@@ -103,7 +105,8 @@ def adjust_page(top, parent, settings):
|
||||
"quickswap": "quickswap",
|
||||
"nobgm": "disablemusic",
|
||||
"reduce_flashing": "reduce_flashing",
|
||||
"shuffle_sfx": "shuffle_sfx"
|
||||
'msu_resume': 'msu_resume',
|
||||
"shuffle_sfx": "shuffle_sfx",
|
||||
}
|
||||
guiargs = Namespace()
|
||||
for option in options:
|
||||
@@ -122,6 +125,58 @@ def adjust_page(top, parent, settings):
|
||||
messagebox.showinfo(title="Success", message="Rom patched successfully")
|
||||
|
||||
adjustButton = Button(self.frames["bottomAdjustFrame"], text='Adjust Rom', command=adjustRom)
|
||||
adjustButton.pack(side=BOTTOM, padx=(5, 0))
|
||||
adjustButton.pack(padx=(5, 0))
|
||||
|
||||
patchFileFrame = Frame(self.frames["bottomAdjustFrame"])
|
||||
patchFileLabel = Label(patchFileFrame, text='BPS Patch: ')
|
||||
self.patchVar = StringVar(value=settings["patch"])
|
||||
patchEntry = Entry(patchFileFrame, textvariable=self.patchVar)
|
||||
|
||||
def PatchSelect():
|
||||
initdir = os.path.join(os.getcwd(), settings['outputpath']) if 'outputpath' in settings else os.getcwd()
|
||||
file = filedialog.askopenfilename(filetypes=[("BPS Files", ".bps"), ("All Files", "*")], initialdir=initdir)
|
||||
if file:
|
||||
settings["patch"] = file
|
||||
self.patchVar.set(file)
|
||||
patchSelectButton = Button(patchFileFrame, text='Select Patch', command=PatchSelect)
|
||||
|
||||
patchFileLabel.pack(side=LEFT)
|
||||
patchEntry.pack(side=LEFT, fill=X, expand=True)
|
||||
patchSelectButton.pack(side=LEFT)
|
||||
patchFileFrame.pack(fill=X)
|
||||
|
||||
def patchRom():
|
||||
if output_path.cached_path is None:
|
||||
output_path.cached_path = top.settings["outputpath"]
|
||||
options = {
|
||||
"heartbeep": "heartbeep",
|
||||
"heartcolor": "heartcolor",
|
||||
"menuspeed": "fastmenu",
|
||||
"owpalettes": "ow_palettes",
|
||||
"uwpalettes": "uw_palettes",
|
||||
"quickswap": "quickswap",
|
||||
"nobgm": "disablemusic",
|
||||
"reduce_flashing": "reduce_flashing",
|
||||
'msu_resume': 'msu_resume',
|
||||
"shuffle_sfx": "shuffle_sfx",
|
||||
}
|
||||
guiargs = Namespace()
|
||||
for option in options:
|
||||
arg = options[option]
|
||||
setattr(guiargs, arg, self.widgets[option].storageVar.get())
|
||||
guiargs.patch = self.patchVar.get()
|
||||
guiargs.baserom = top.pages["randomizer"].pages["generation"].widgets["rom"].storageVar.get()
|
||||
guiargs.sprite = self.sprite
|
||||
guiargs.outputpath = os.path.dirname(guiargs.patch)
|
||||
try:
|
||||
patch(args=guiargs)
|
||||
except Exception as e:
|
||||
logging.exception(e)
|
||||
messagebox.showerror(title="Error while creating seed", message=str(e))
|
||||
else:
|
||||
messagebox.showinfo(title="Success", message="Rom patched successfully")
|
||||
|
||||
patchButton = Button(self.frames["bottomAdjustFrame"], text='Patch Rom', command=patchRom)
|
||||
patchButton.pack(side=BOTTOM, padx=(5, 0))
|
||||
|
||||
return self,settings
|
||||
|
||||
@@ -275,4 +275,9 @@ def create_guiargs(parent):
|
||||
|
||||
guiargs = update_deprecated_args(guiargs)
|
||||
|
||||
# Key drop shuffle stuff
|
||||
if guiargs.keydropshuffle:
|
||||
guiargs.dropshuffle = 1
|
||||
guiargs.pottery = 'keys' if guiargs.pottery == 'none' else guiargs.pottery
|
||||
|
||||
return guiargs
|
||||
|
||||
@@ -52,7 +52,7 @@ def loadcliargs(gui, args, settings=None):
|
||||
gui.pages[mainpage].pages[subpage].widgets[widget].label.configure(text=label)
|
||||
gui.pages[mainpage].pages[subpage].widgets[widget].storageVar.set(args[arg])
|
||||
# If we're on the Game Options page and it's not about Hints
|
||||
if subpage == "gameoptions" and not widget == "hints":
|
||||
if subpage == "gameoptions" and widget not in ["hints", "collection_rate"]:
|
||||
# Check if we've got settings
|
||||
# Check if we've got the widget in Adjust settings
|
||||
hasSettings = settings is not None
|
||||
|
||||
@@ -27,7 +27,7 @@ def entrando_page(parent):
|
||||
for key in dictWidgets:
|
||||
self.widgets[key] = dictWidgets[key]
|
||||
packAttrs = {"anchor":E}
|
||||
if self.widgets[key].type == "checkbox":
|
||||
if self.widgets[key].type == "checkbox" or key == "openpyramid":
|
||||
packAttrs["anchor"] = W
|
||||
self.widgets[key].pack(packAttrs)
|
||||
|
||||
|
||||
@@ -15,12 +15,16 @@ def overworld_page(parent):
|
||||
|
||||
# Load Overworld Shuffle option widgets as defined by JSON file
|
||||
# Defns include frame name, widget type, widget options, widget placement attributes
|
||||
# These get split left & right
|
||||
self.frames["topOverworldFrame"] = Frame(self)
|
||||
self.frames["leftOverworldFrame"] = Frame(self)
|
||||
self.frames["rightOverworldFrame"] = Frame(self)
|
||||
|
||||
self.frames["topOverworldFrame"].pack(side=TOP, anchor=NW)
|
||||
self.frames["leftOverworldFrame"].pack(side=LEFT, anchor=NW, fill=Y)
|
||||
self.frames["rightOverworldFrame"].pack(anchor=NW, fill=Y)
|
||||
|
||||
shuffleLabel = Label(self.frames["topOverworldFrame"], text="Shuffle: ")
|
||||
shuffleLabel.pack(side=LEFT)
|
||||
|
||||
with open(os.path.join("resources","app","gui","randomize","overworld","widgets.json")) as overworldWidgets:
|
||||
myDict = json.load(overworldWidgets)
|
||||
@@ -33,7 +37,7 @@ def overworld_page(parent):
|
||||
packAttrs = {"side":LEFT, "pady":(18,0)}
|
||||
elif key == "overworldflute":
|
||||
packAttrs["pady"] = (20,0)
|
||||
elif key in ["whirlpool", "mixed"]:
|
||||
elif key in ["mixed", "whirlpool"]:
|
||||
packAttrs = {"anchor":W, "padx":(79,0)}
|
||||
|
||||
self.widgets[key].pack(packAttrs)
|
||||
|
||||
159
source/item/District.py
Normal file
159
source/item/District.py
Normal file
@@ -0,0 +1,159 @@
|
||||
from collections import deque
|
||||
|
||||
from BaseClasses import CollectionState, RegionType
|
||||
from Dungeons import dungeon_table
|
||||
from OWEdges import OWTileRegions
|
||||
|
||||
class District(object):
|
||||
|
||||
def __init__(self, name, locations, entrances=None, dungeon=None):
|
||||
self.name = name
|
||||
self.dungeon = dungeon
|
||||
self.locations = locations
|
||||
self.entrances = entrances if entrances else []
|
||||
self.sphere_one = False
|
||||
|
||||
self.dungeons = set()
|
||||
self.access_points = set()
|
||||
|
||||
|
||||
def create_districts(world):
|
||||
world.districts = {}
|
||||
for p in range(1, world.players + 1):
|
||||
create_district_helper(world, p)
|
||||
|
||||
|
||||
def create_district_helper(world, player):
|
||||
districts = {}
|
||||
kak_locations = {'Bottle Merchant', 'Kakariko Tavern', 'Maze Race'}
|
||||
nw_lw_locations = {'Mushroom', 'Master Sword Pedestal'}
|
||||
central_lw_locations = {'Sunken Treasure', 'Flute Spot'}
|
||||
desert_locations = {'Purple Chest', 'Desert Ledge', 'Bombos Tablet'}
|
||||
lake_locations = {'Hobo', 'Lake Hylia Island'}
|
||||
east_lw_locations = {"Zora's Ledge", 'King Zora'}
|
||||
lw_dm_locations = {'Old Man', 'Spectacle Rock', 'Ether Tablet', 'Floating Island'}
|
||||
east_dw_locations = {'Pyramid', 'Catfish'}
|
||||
south_dw_locations = {'Stumpy', 'Digging Game'}
|
||||
voo_north_locations = {'Bumper Cave Ledge'}
|
||||
ddm_locations = set()
|
||||
|
||||
kak_entrances = ['Kakariko Well Cave', 'Bat Cave Cave', 'Elder House (East)', 'Elder House (West)',
|
||||
'Two Brothers House (East)', 'Two Brothers House (West)', 'Blinds Hideout', 'Chicken House',
|
||||
'Blacksmiths Hut', 'Sick Kids House', 'Snitch Lady (East)', 'Snitch Lady (West)',
|
||||
'Bush Covered House', 'Tavern (Front)', 'Light World Bomb Hut', 'Kakariko Shop', 'Library',
|
||||
'Kakariko Gamble Game', 'Kakariko Well Drop', 'Bat Cave Drop', 'Tavern North']
|
||||
nw_lw_entrances = ['North Fairy Cave', 'Lost Woods Hideout Stump', 'Lumberjack Tree Cave', 'Sanctuary',
|
||||
'Old Man Cave (West)', 'Death Mountain Return Cave (West)', 'Kings Grave', 'Lost Woods Gamble',
|
||||
'Fortune Teller (Light)', 'Bonk Rock Cave', 'Lumberjack House', 'North Fairy Cave Drop',
|
||||
'Lost Woods Hideout Drop', 'Lumberjack Tree Tree', 'Sanctuary Grave', 'Graveyard Cave']
|
||||
central_lw_entrances = ['Links House', 'Hyrule Castle Entrance (South)', 'Hyrule Castle Entrance (West)',
|
||||
'Hyrule Castle Entrance (East)', 'Agahnims Tower', 'Hyrule Castle Secret Entrance Stairs',
|
||||
'Dam', 'Bonk Fairy (Light)', 'Light Hype Fairy', 'Hyrule Castle Secret Entrance Drop',
|
||||
'Cave 45']
|
||||
desert_entrances = ['Desert Palace Entrance (South)', 'Desert Palace Entrance (West)',
|
||||
'Desert Palace Entrance (North)', 'Desert Palace Entrance (East)', 'Desert Fairy',
|
||||
'Aginahs Cave', '50 Rupee Cave', 'Checkerboard Cave']
|
||||
lake_entrances = ['Capacity Upgrade', 'Mini Moldorm Cave', 'Good Bee Cave', '20 Rupee Cave', 'Ice Rod Cave',
|
||||
'Cave Shop (Lake Hylia)', 'Lake Hylia Fortune Teller']
|
||||
east_lw_entrances = ['Eastern Palace', 'Waterfall of Wishing', 'Lake Hylia Fairy', 'Sahasrahlas Hut',
|
||||
'Long Fairy Cave', 'Potion Shop']
|
||||
lw_dm_entrances = ['Tower of Hera', 'Old Man Cave (East)', 'Old Man House (Bottom)', 'Old Man House (Top)',
|
||||
'Death Mountain Return Cave (East)', 'Spectacle Rock Cave Peak', 'Spectacle Rock Cave',
|
||||
'Spectacle Rock Cave (Bottom)', 'Paradox Cave (Bottom)', 'Paradox Cave (Middle)',
|
||||
'Paradox Cave (Top)', 'Fairy Ascension Cave (Bottom)', 'Fairy Ascension Cave (Top)',
|
||||
'Spiral Cave', 'Spiral Cave (Bottom)', 'Hookshot Fairy', 'Mimic Cave']
|
||||
east_dw_entrances = ['Palace of Darkness', 'Pyramid Entrance', 'Pyramid Fairy', 'East Dark World Hint',
|
||||
'Palace of Darkness Hint', 'Dark Lake Hylia Fairy', 'Dark World Potion Shop', 'Pyramid Hole']
|
||||
south_dw_entrances = ['Ice Palace', 'Swamp Palace', 'Dark Lake Hylia Ledge Fairy',
|
||||
'Dark Lake Hylia Ledge Spike Cave', 'Dark Lake Hylia Ledge Hint', 'Hype Cave',
|
||||
'Bonk Fairy (Dark)', 'Archery Game', 'Big Bomb Shop', 'Dark Lake Hylia Shop', ]
|
||||
voo_north_entrances = ['Thieves Town', 'Skull Woods First Section Door', 'Skull Woods Second Section Door (East)',
|
||||
'Skull Woods Second Section Door (West)', 'Skull Woods Final Section',
|
||||
'Bumper Cave (Bottom)', 'Bumper Cave (Top)', 'Brewery', 'C-Shaped House', 'Chest Game',
|
||||
'Dark World Hammer Peg Cave', 'Red Shield Shop', 'Dark Sanctuary Hint',
|
||||
'Fortune Teller (Dark)', 'Dark World Shop', 'Dark World Lumberjack Shop',
|
||||
'Skull Woods First Section Hole (West)', 'Skull Woods First Section Hole (East)',
|
||||
'Skull Woods First Section Hole (North)', 'Skull Woods Second Section Hole']
|
||||
mire_entrances = ['Misery Mire', 'Mire Shed', 'Dark Desert Hint', 'Dark Desert Fairy']
|
||||
ddm_entrances = ['Turtle Rock', 'Dark Death Mountain Ledge (West)', 'Dark Death Mountain Ledge (East)',
|
||||
'Turtle Rock Isolated Ledge Entrance', 'Superbunny Cave (Top)', 'Superbunny Cave (Bottom)',
|
||||
'Hookshot Cave', 'Hookshot Cave Back Entrance', 'Ganons Tower', 'Spike Cave',
|
||||
'Cave Shop (Dark Death Mountain)', 'Dark Death Mountain Fairy']
|
||||
|
||||
if world.is_tile_swapped(0x1b, player):
|
||||
east_dw_entrances.remove('Pyramid Entrance')
|
||||
east_dw_entrances.remove('Pyramid Hole')
|
||||
central_lw_entrances.append('Inverted Pyramid Entrance')
|
||||
central_lw_entrances.append('Inverted Pyramid Hole')
|
||||
|
||||
districts['Kakariko'] = District('Kakariko', kak_locations, entrances=kak_entrances)
|
||||
districts['Northwest Hyrule'] = District('Northwest Hyrule', nw_lw_locations, entrances=nw_lw_entrances)
|
||||
districts['Central Hyrule'] = District('Central Hyrule', central_lw_locations, entrances=central_lw_entrances)
|
||||
districts['The Desert Area'] = District('Desert', desert_locations, entrances=desert_entrances)
|
||||
districts['Lake Hylia'] = District('Lake Hylia', lake_locations, entrances=lake_entrances)
|
||||
districts['Eastern Hyrule'] = District('Eastern Hyrule', east_lw_locations, entrances=east_lw_entrances)
|
||||
districts['Death Mountain'] = District('Death Mountain', lw_dm_locations, entrances=lw_dm_entrances)
|
||||
districts['East Dark World'] = District('East Dark World', east_dw_locations, entrances=east_dw_entrances)
|
||||
districts['South Dark World'] = District('South Dark World', south_dw_locations, entrances=south_dw_entrances)
|
||||
districts['Northwest Dark World'] = District('Northwest Dark World', voo_north_locations,
|
||||
entrances=voo_north_entrances)
|
||||
districts['The Mire Area'] = District('The Mire', set(), entrances=mire_entrances)
|
||||
districts['Dark Death Mountain'] = District('Dark Death Mountain', ddm_locations, entrances=ddm_entrances)
|
||||
districts.update({x: District(x, set(), dungeon=x) for x in dungeon_table.keys()})
|
||||
|
||||
world.districts[player] = districts
|
||||
|
||||
|
||||
def resolve_districts(world):
|
||||
create_districts(world)
|
||||
state = CollectionState(world)
|
||||
state.sweep_for_events()
|
||||
for player in range(1, world.players + 1):
|
||||
# these are not static for OWR - but still important
|
||||
inaccessible = [r for r in inaccessible_regions_std if not world.is_tile_swapped(OWTileRegions[r], player)]
|
||||
inaccessible = inaccessible + [r for r in inaccessible_regions_inv if world.is_tile_swapped(OWTileRegions[r], player)]
|
||||
check_set = find_reachable_locations(state, player)
|
||||
for name, district in world.districts[player].items():
|
||||
if district.dungeon:
|
||||
layout = world.dungeon_layouts[player][district.dungeon]
|
||||
district.locations.update([l.name for r in layout.master_sector.regions
|
||||
for l in r.locations if not l.item and l.real])
|
||||
else:
|
||||
for entrance in district.entrances:
|
||||
ent = world.get_entrance(entrance, player)
|
||||
queue = deque([ent.connected_region])
|
||||
visited = set()
|
||||
while len(queue) > 0:
|
||||
region = queue.pop()
|
||||
visited.add(region)
|
||||
if region.type == RegionType.Cave:
|
||||
for location in region.locations:
|
||||
if not location.item and location.real:
|
||||
district.locations.add(location.name)
|
||||
for ext in region.exits:
|
||||
if ext.connected_region is not None and ext.connected_region not in visited:
|
||||
queue.appendleft(ext.connected_region)
|
||||
elif region.type == RegionType.Dungeon and region.dungeon:
|
||||
district.dungeons.add(region.dungeon.name)
|
||||
elif region.name in inaccessible:
|
||||
district.access_points.add(region)
|
||||
|
||||
district.sphere_one = len(check_set.intersection(district.locations)) > 0
|
||||
|
||||
|
||||
def find_reachable_locations(state, player):
|
||||
check_set = set()
|
||||
for region in state.reachable_regions[player]:
|
||||
for location in region.locations:
|
||||
if location.can_reach(state) and not location.forced_item and location.real:
|
||||
check_set.add(location.name)
|
||||
return check_set
|
||||
|
||||
|
||||
inaccessible_regions_std = {'Desert Palace Stairs', 'Bumper Cave Ledge', 'Skull Woods Forest (West)',
|
||||
'Dark Death Mountain Ledge', 'Dark Death Mountain Isolated Ledge',
|
||||
'Dark Death Mountain Floating Island'}
|
||||
|
||||
|
||||
inaccessible_regions_inv = {'Desert Palace Stairs', 'Maze Race Ledge', 'Desert Ledge',
|
||||
'Desert Palace Entrance (North) Spot', 'Hyrule Castle Ledge', 'Mountain Entry Ledge'}
|
||||
813
source/item/FillUtil.py
Normal file
813
source/item/FillUtil.py
Normal file
@@ -0,0 +1,813 @@
|
||||
import RaceRandom as random
|
||||
import logging
|
||||
from collections import defaultdict
|
||||
|
||||
from source.item.District import resolve_districts
|
||||
from BaseClasses import PotItem, PotFlags
|
||||
from DoorShuffle import validate_vanilla_reservation
|
||||
from Dungeons import dungeon_table
|
||||
from Items import item_table, ItemFactory
|
||||
from PotShuffle import vanilla_pots
|
||||
|
||||
|
||||
class ItemPoolConfig(object):
|
||||
|
||||
def __init__(self):
|
||||
self.location_groups = None
|
||||
self.static_placement = None
|
||||
self.item_pool = None
|
||||
self.placeholders = None
|
||||
self.reserved_locations = defaultdict(set)
|
||||
|
||||
self.recorded_choices = []
|
||||
|
||||
|
||||
class LocationGroup(object):
|
||||
def __init__(self, name):
|
||||
self.name = name
|
||||
self.locations = []
|
||||
|
||||
# flags
|
||||
self.keyshuffle = False
|
||||
self.shopsanity = False
|
||||
self.retro = False
|
||||
|
||||
def locs(self, locs):
|
||||
self.locations = list(locs)
|
||||
return self
|
||||
|
||||
def flags(self, k, s=False, r=False):
|
||||
self.keyshuffle = k
|
||||
self.shopsanity = s
|
||||
self.retro = r
|
||||
return self
|
||||
|
||||
|
||||
def create_item_pool_config(world):
|
||||
world.item_pool_config = config = ItemPoolConfig()
|
||||
player_set = set()
|
||||
for player in range(1, world.players+1):
|
||||
if world.restrict_boss_items[player] != 'none':
|
||||
player_set.add(player)
|
||||
if world.restrict_boss_items[player] == 'dungeon':
|
||||
for dungeon, info in dungeon_table.items():
|
||||
if info.prize:
|
||||
d_name = "Thieves' Town" if dungeon.startswith('Thieves') else dungeon
|
||||
config.reserved_locations[player].add(f'{d_name} - Boss')
|
||||
for dungeon in world.dungeons:
|
||||
if world.restrict_boss_items[dungeon.player] != 'none':
|
||||
for item in dungeon.all_items:
|
||||
if item.map or item.compass:
|
||||
item.advancement = True
|
||||
if world.algorithm == 'vanilla_fill':
|
||||
config.static_placement = {}
|
||||
config.location_groups = {}
|
||||
for player in range(1, world.players + 1):
|
||||
config.static_placement[player] = defaultdict(list)
|
||||
config.static_placement[player].update(vanilla_mapping)
|
||||
if world.dropshuffle[player]:
|
||||
for item, locs in keydrop_vanilla_mapping.items():
|
||||
config.static_placement[player][item].extend(locs)
|
||||
if world.pottery[player] not in ['none', 'cave']:
|
||||
for item, locs in potkeys_vanilla_mapping.items():
|
||||
config.static_placement[player][item].extend(locs)
|
||||
if world.pottery[player] in ['lottery', 'cave', 'dungeon']:
|
||||
for super_tile, pot_list in vanilla_pots.items():
|
||||
for pot_index, pot in enumerate(pot_list):
|
||||
if pot.item not in [PotItem.Key, PotItem.Hole, PotItem.Switch]:
|
||||
item = pot_items[pot.item]
|
||||
descriptor = 'Large Block' if pot.flags & PotFlags.Block else f'Pot #{pot_index+1}'
|
||||
location = f'{pot.room} {descriptor}'
|
||||
config.static_placement[player][item].append(location)
|
||||
if world.shopsanity[player]:
|
||||
for item, locs in shop_vanilla_mapping.items():
|
||||
config.static_placement[player][item].extend(locs)
|
||||
if world.retro[player]:
|
||||
for item, locs in retro_vanilla_mapping.items():
|
||||
config.static_placement[player][item].extend(locs)
|
||||
# universal keys
|
||||
universal_key_locations = []
|
||||
for item, locs in vanilla_mapping.items():
|
||||
if 'Small Key' in item:
|
||||
universal_key_locations.extend(locs)
|
||||
if world.dropshuffle[player]:
|
||||
for item, locs in keydrop_vanilla_mapping.items():
|
||||
if 'Small Key' in item:
|
||||
universal_key_locations.extend(locs)
|
||||
if world.pottery[player] not in ['none', 'cave']:
|
||||
for item, locs in potkeys_vanilla_mapping.items():
|
||||
universal_key_locations.extend(locs)
|
||||
if world.shopsanity[player]:
|
||||
single_arrow_placement = list(shop_vanilla_mapping['Red Potion'])
|
||||
single_arrow_placement.append('Red Shield Shop - Right')
|
||||
config.static_placement[player]['Single Arrow'] = single_arrow_placement
|
||||
universal_key_locations.extend(shop_vanilla_mapping['Small Heart'])
|
||||
universal_key_locations.extend(shop_vanilla_mapping['Blue Shield'])
|
||||
config.static_placement[player]['Small Key (Universal)'] = universal_key_locations
|
||||
config.location_groups[player] = [
|
||||
LocationGroup('Major').locs(mode_grouping['Overworld Major'] + mode_grouping['Big Chests'] + mode_grouping['Heart Containers']),
|
||||
LocationGroup('bkhp').locs(mode_grouping['Heart Pieces']),
|
||||
LocationGroup('bktrash').locs(mode_grouping['Overworld Trash'] + mode_grouping['Dungeon Trash']),
|
||||
LocationGroup('bkgt').locs(mode_grouping['GT Trash'])]
|
||||
for loc_name in mode_grouping['Big Chests'] + mode_grouping['Heart Containers']:
|
||||
config.reserved_locations[player].add(loc_name)
|
||||
elif world.algorithm == 'major_only':
|
||||
config.location_groups = [
|
||||
LocationGroup('MajorItems'),
|
||||
LocationGroup('Backup')
|
||||
]
|
||||
config.item_pool = {}
|
||||
init_set = mode_grouping['Overworld Major'] + mode_grouping['Big Chests'] + mode_grouping['Heart Containers']
|
||||
for player in range(1, world.players + 1):
|
||||
groups = LocationGroup('Major').locs(init_set)
|
||||
if world.bigkeyshuffle[player]:
|
||||
groups.locations.extend(mode_grouping['Big Keys'])
|
||||
if world.dropshuffle[player] != 'none':
|
||||
groups.locations.extend(mode_grouping['Big Key Drops'])
|
||||
if world.keyshuffle[player]:
|
||||
groups.locations.extend(mode_grouping['Small Keys'])
|
||||
if world.dropshuffle[player] != 'none':
|
||||
groups.locations.extend(mode_grouping['Key Drops'])
|
||||
if world.pottery[player] not in ['none', 'cave']:
|
||||
groups.locations.extend(mode_grouping['Pot Keys'])
|
||||
if world.compassshuffle[player]:
|
||||
groups.locations.extend(mode_grouping['Compasses'])
|
||||
if world.mapshuffle[player]:
|
||||
groups.locations.extend(mode_grouping['Maps'])
|
||||
if world.shopsanity[player]:
|
||||
groups.locations.append('Capacity Upgrade - Left')
|
||||
groups.locations.append('Capacity Upgrade - Right')
|
||||
if world.retro[player]:
|
||||
if world.shopsanity[player]:
|
||||
groups.locations.extend(retro_vanilla_mapping['Heart Container'])
|
||||
groups.locations.append('Old Man Sword Cave Item 1')
|
||||
config.item_pool[player] = determine_major_items(world, player)
|
||||
config.location_groups[0].locations = set(groups.locations)
|
||||
config.reserved_locations[player].update(groups.locations)
|
||||
backup = (mode_grouping['Heart Pieces'] + mode_grouping['Dungeon Trash'] + mode_grouping['Shops']
|
||||
+ mode_grouping['Overworld Trash'] + mode_grouping['GT Trash'] + mode_grouping['RetroShops'])
|
||||
config.location_groups[1].locations = set(backup)
|
||||
elif world.algorithm == 'dungeon_only':
|
||||
config.location_groups = [
|
||||
LocationGroup('Dungeons'),
|
||||
LocationGroup('Backup')
|
||||
]
|
||||
config.item_pool = {}
|
||||
dungeon_set = (mode_grouping['Big Chests'] + mode_grouping['Dungeon Trash'] + mode_grouping['Big Keys'] +
|
||||
mode_grouping['Heart Containers'] + mode_grouping['GT Trash'] + mode_grouping['Small Keys'] +
|
||||
mode_grouping['Compasses'] + mode_grouping['Maps'] + mode_grouping['Key Drops'] +
|
||||
mode_grouping['Big Key Drops'])
|
||||
for player in range(1, world.players + 1):
|
||||
config.item_pool[player] = determine_major_items(world, player)
|
||||
config.location_groups[0].locations = set(dungeon_set)
|
||||
backup = (mode_grouping['Heart Pieces'] + mode_grouping['Overworld Major']
|
||||
+ mode_grouping['Overworld Trash'] + mode_grouping['Shops'] + mode_grouping['RetroShops'])
|
||||
config.location_groups[1].locations = set(backup)
|
||||
|
||||
|
||||
def district_item_pool_config(world):
|
||||
resolve_districts(world)
|
||||
if world.algorithm == 'district':
|
||||
config = world.item_pool_config
|
||||
config.location_groups = [
|
||||
LocationGroup('Districts'),
|
||||
]
|
||||
item_cnt = 0
|
||||
config.item_pool = {}
|
||||
for player in range(1, world.players + 1):
|
||||
config.item_pool[player] = determine_major_items(world, player)
|
||||
item_cnt += count_major_items(config, world, player)
|
||||
# set district choices
|
||||
district_choices = {}
|
||||
for p in range(1, world.players + 1):
|
||||
for name, district in world.districts[p].items():
|
||||
adjustment = 0
|
||||
if district.dungeon:
|
||||
adjustment = len([i for i in world.get_dungeon(name, p).all_items
|
||||
if i.is_inside_dungeon_item(world)])
|
||||
dist_len = len(district.locations) - adjustment
|
||||
if name not in district_choices:
|
||||
district_choices[name] = (district.sphere_one, dist_len)
|
||||
else:
|
||||
so, amt = district_choices[name]
|
||||
district_choices[name] = (so or district.sphere_one, amt + dist_len)
|
||||
|
||||
chosen_locations = defaultdict(set)
|
||||
location_cnt = 0
|
||||
|
||||
# choose a sphere one district
|
||||
sphere_one_choices = [d for d, info in district_choices.items() if info[0]]
|
||||
sphere_one = random.choice(sphere_one_choices)
|
||||
so, amt = district_choices[sphere_one]
|
||||
location_cnt += amt
|
||||
for player in range(1, world.players + 1):
|
||||
for location in world.districts[player][sphere_one].locations:
|
||||
chosen_locations[location].add(player)
|
||||
del district_choices[sphere_one]
|
||||
config.recorded_choices.append(sphere_one)
|
||||
|
||||
scale_factors = defaultdict(int)
|
||||
scale_total = 0
|
||||
for p in range(1, world.players + 1):
|
||||
ent = 'Agahnims Tower' if world.is_atgt_swapped(player) else 'Ganons Tower'
|
||||
dungeon = world.get_entrance(ent, p).connected_region.dungeon
|
||||
if dungeon:
|
||||
scale = world.crystals_needed_for_gt[p]
|
||||
scale_total += scale
|
||||
scale_factors[dungeon.name] += scale
|
||||
scale_total = max(1, scale_total)
|
||||
scale_divisors = defaultdict(lambda: 1)
|
||||
scale_divisors.update(scale_factors)
|
||||
|
||||
while location_cnt < item_cnt:
|
||||
weights = [scale_total / scale_divisors[d] for d in district_choices.keys()]
|
||||
choice = random.choices(list(district_choices.keys()), weights=weights, k=1)[0]
|
||||
so, amt = district_choices[choice]
|
||||
location_cnt += amt
|
||||
for player in range(1, world.players + 1):
|
||||
for location in world.districts[player][choice].locations:
|
||||
chosen_locations[location].add(player)
|
||||
del district_choices[choice]
|
||||
config.recorded_choices.append(choice)
|
||||
config.placeholders = location_cnt - item_cnt
|
||||
config.location_groups[0].locations = chosen_locations
|
||||
|
||||
|
||||
def location_prefilled(location, world, player):
|
||||
if world.swords[player] == 'vanilla':
|
||||
return location in vanilla_swords
|
||||
if world.goal[player] in ['pedestal', 'trinity']:
|
||||
return location == 'Master Sword Pedestal'
|
||||
return False
|
||||
|
||||
|
||||
def previously_reserved(location, world, player):
|
||||
if '- Boss' in location.name:
|
||||
if world.restrict_boss_items[player] == 'mapcompass' and (not world.compassshuffle[player]
|
||||
or not world.mapshuffle[player]):
|
||||
return True
|
||||
if world.restrict_boss_items[player] == 'dungeon' and (not world.compassshuffle[player]
|
||||
or not world.mapshuffle[player]
|
||||
or not world.bigkeyshuffle[player]
|
||||
or not (world.keyshuffle[player] or world.retro[player])):
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
def massage_item_pool(world):
|
||||
player_pool = defaultdict(list)
|
||||
for item in world.itempool:
|
||||
player_pool[item.player].append(item)
|
||||
for dungeon in world.dungeons:
|
||||
for item in dungeon.all_items:
|
||||
if item.is_inside_dungeon_item(world):
|
||||
player_pool[item.player].append(item)
|
||||
player_locations = defaultdict(list)
|
||||
for player in player_pool:
|
||||
player_locations[player] = [x for x in world.get_unfilled_locations(player) if '- Prize' not in x.name]
|
||||
discrepancy = len(player_pool[player]) - len(player_locations[player])
|
||||
if discrepancy:
|
||||
trash_options = [x for x in player_pool[player] if x.name in trash_items]
|
||||
random.shuffle(trash_options)
|
||||
trash_options = sorted(trash_options, key=lambda x: trash_items[x.name], reverse=True)
|
||||
while discrepancy > 0 and len(trash_options) > 0:
|
||||
deleted = trash_options.pop()
|
||||
world.itempool.remove(deleted)
|
||||
discrepancy -= 1
|
||||
if discrepancy > 0:
|
||||
raise Exception(f'Too many required items in pool, {discrepancy} items cannot be placed')
|
||||
if world.item_pool_config.placeholders is not None:
|
||||
removed = 0
|
||||
single_rupees = [item for item in world.itempool if item.name == 'Rupee (1)']
|
||||
removed += len(single_rupees)
|
||||
for x in single_rupees:
|
||||
world.itempool.remove(x)
|
||||
if removed < world.item_pool_config.placeholders:
|
||||
trash_options = [x for x in world.itempool if x.name in trash_items]
|
||||
random.shuffle(trash_options)
|
||||
trash_options = sorted(trash_options, key=lambda x: trash_items[x.name], reverse=True)
|
||||
while removed < world.item_pool_config.placeholders:
|
||||
if len(trash_options) == 0:
|
||||
logging.getLogger('').warning(f'Too many good items in pool, not enough room for placeholders')
|
||||
deleted = trash_options.pop()
|
||||
world.itempool.remove(deleted)
|
||||
removed += 1
|
||||
if world.item_pool_config.placeholders > len(single_rupees):
|
||||
for _ in range(world.item_pool_config.placeholders-len(single_rupees)):
|
||||
single_rupees.append(ItemFactory('Rupee (1)', random.randint(1, world.players)))
|
||||
placeholders = random.sample(single_rupees, world.item_pool_config.placeholders)
|
||||
world.itempool += placeholders
|
||||
removed -= len(placeholders)
|
||||
for _ in range(removed):
|
||||
world.itempool.append(ItemFactory('Rupees (5)', random.randint(1, world.players)))
|
||||
|
||||
|
||||
def replace_trash_item(item_pool, replacement):
|
||||
trash_options = [x for x in item_pool if x.name in trash_items]
|
||||
random.shuffle(trash_options)
|
||||
trash_options = sorted(trash_options, key=lambda x: trash_items[x.name], reverse=True)
|
||||
if len(trash_options) == 0:
|
||||
logging.getLogger('').warning(f'Too many good items in pool, not enough room for placeholders')
|
||||
deleted = trash_options.pop()
|
||||
item_pool.remove(deleted)
|
||||
replace_item = ItemFactory(replacement, deleted.player)
|
||||
item_pool.append(replace_item)
|
||||
return replace_item
|
||||
|
||||
|
||||
def validate_reservation(location, dungeon, world, player):
|
||||
world.item_pool_config.reserved_locations[player].add(location.name)
|
||||
if world.doorShuffle[player] != 'vanilla':
|
||||
return True # we can generate the dungeon somehow most likely
|
||||
if validate_vanilla_reservation(dungeon, world, player):
|
||||
return True
|
||||
world.item_pool_config.reserved_locations[player].remove(location.name)
|
||||
return False
|
||||
|
||||
|
||||
def count_major_items(config, world, player):
|
||||
return sum(1 for x in world.itempool if x.name in config.item_pool[player] and x.player == player)
|
||||
|
||||
|
||||
def determine_major_items(world, player):
|
||||
major_item_set = set(major_items)
|
||||
if world.progressive == 'off':
|
||||
pass # now what?
|
||||
if world.bigkeyshuffle[player]:
|
||||
major_item_set.update({x for x, y in item_table.items() if y[2] == 'BigKey'})
|
||||
if world.keyshuffle[player]:
|
||||
major_item_set.update({x for x, y in item_table.items() if y[2] == 'SmallKey'})
|
||||
if world.compassshuffle[player]:
|
||||
major_item_set.update({x for x, y in item_table.items() if y[2] == 'Compass'})
|
||||
if world.mapshuffle[player]:
|
||||
major_item_set.update({x for x, y in item_table.items() if y[2] == 'Map'})
|
||||
if world.shopsanity[player]:
|
||||
major_item_set.add('Bomb Upgrade (+5)')
|
||||
major_item_set.add('Arrow Upgrade (+5)')
|
||||
if world.retro[player]:
|
||||
major_item_set.add('Single Arrow')
|
||||
major_item_set.add('Small Key (Universal)')
|
||||
if world.goal in ['triforcehunt', 'trinity']:
|
||||
major_item_set.add('Triforce Piece')
|
||||
if world.bombbag[player]:
|
||||
major_item_set.add('Bomb Upgrade (+10)')
|
||||
return major_item_set
|
||||
|
||||
|
||||
def classify_major_items(world):
|
||||
if world.algorithm in ['major_only', 'dungeon_only', 'district']:
|
||||
config = world.item_pool_config
|
||||
for item in world.itempool:
|
||||
if item.name in config.item_pool[item.player]:
|
||||
if not item.advancement and not item.priority:
|
||||
if item.smallkey or item.bigkey:
|
||||
item.advancement = True
|
||||
else:
|
||||
item.priority = True
|
||||
else:
|
||||
if item.priority:
|
||||
item.priority = False
|
||||
|
||||
|
||||
def vanilla_fallback(item_to_place, locations, world):
|
||||
if item_to_place.is_inside_dungeon_item(world):
|
||||
return [x for x in locations if x.name in vanilla_fallback_dungeon_set
|
||||
and x.parent_region.dungeon and x.parent_region.dungeon.name == item_to_place.dungeon]
|
||||
return []
|
||||
|
||||
|
||||
def filter_locations(item_to_place, locations, world, vanilla_skip=False, potion=False):
|
||||
if world.algorithm == 'vanilla_fill':
|
||||
config, filtered = world.item_pool_config, []
|
||||
item_name = 'Bottle' if item_to_place.name.startswith('Bottle') else item_to_place.name
|
||||
if item_name in config.static_placement[item_to_place.player]:
|
||||
restricted = config.static_placement[item_to_place.player][item_name]
|
||||
filtered = [l for l in locations if l.player == item_to_place.player and l.name in restricted]
|
||||
if vanilla_skip and len(filtered) == 0:
|
||||
return filtered
|
||||
i = 0
|
||||
while len(filtered) <= 0:
|
||||
if i >= len(config.location_groups[item_to_place.player]):
|
||||
return locations
|
||||
restricted = config.location_groups[item_to_place.player][i].locations
|
||||
filtered = [l for l in locations if l.player == item_to_place.player and l.name in restricted]
|
||||
i += 1
|
||||
return filtered
|
||||
if world.algorithm in ['major_only', 'dungeon_only']:
|
||||
config = world.item_pool_config
|
||||
if item_to_place.name in config.item_pool[item_to_place.player]:
|
||||
restricted = config.location_groups[0].locations
|
||||
filtered = [l for l in locations if l.name in restricted]
|
||||
if len(filtered) == 0:
|
||||
restricted = config.location_groups[1].locations
|
||||
filtered = [l for l in locations if l.name in restricted]
|
||||
# bias toward certain location in overflow? (thinking about this for major_bias)
|
||||
return filtered if len(filtered) > 0 else locations
|
||||
if world.algorithm == 'district':
|
||||
config = world.item_pool_config
|
||||
if item_to_place == 'Placeholder' or item_to_place.name in config.item_pool[item_to_place.player]:
|
||||
restricted = config.location_groups[0].locations
|
||||
filtered = [l for l in locations if l.name in restricted and l.player in restricted[l.name]]
|
||||
return filtered if len(filtered) > 0 else locations
|
||||
elif potion:
|
||||
restricted = config.location_groups[0].locations
|
||||
filtered = [l for l in locations if l.name not in restricted or l.player not in restricted[l.name]]
|
||||
if len(filtered) == 0:
|
||||
raise RuntimeError('Can\'t sell potion of a certain type due to district restriction')
|
||||
return filtered
|
||||
return locations
|
||||
|
||||
|
||||
def filter_pot_locations(locations, world):
|
||||
if world.algorithm == 'district':
|
||||
config = world.item_pool_config
|
||||
restricted = config.location_groups[0].locations
|
||||
filtered = [l for l in locations if l.name not in restricted or l.player not in restricted[l.name]]
|
||||
return filtered if len(filtered) > 0 else locations
|
||||
if world.algorithm == 'vanilla_fill':
|
||||
filtered = [l for l in locations if l.pot and l.pot.item in [PotItem.Chicken, PotItem.BigMagic]]
|
||||
return filtered if len(filtered) > 0 else locations
|
||||
return locations
|
||||
|
||||
|
||||
|
||||
vanilla_mapping = {
|
||||
'Green Pendant': ['Eastern Palace - Prize'],
|
||||
'Red Pendant': ['Desert Palace - Prize', 'Tower of Hera - Prize'],
|
||||
'Blue Pendant': ['Desert Palace - Prize', 'Tower of Hera - Prize'],
|
||||
'Crystal 1': ['Palace of Darkness - Prize', 'Swamp Palace - Prize', 'Thieves\' Town - Prize',
|
||||
'Skull Woods - Prize', 'Turtle Rock - Prize'],
|
||||
'Crystal 2': ['Palace of Darkness - Prize', 'Swamp Palace - Prize', 'Thieves\' Town - Prize',
|
||||
'Skull Woods - Prize', 'Turtle Rock - Prize'],
|
||||
'Crystal 3': ['Palace of Darkness - Prize', 'Swamp Palace - Prize', 'Thieves\' Town - Prize',
|
||||
'Skull Woods - Prize', 'Turtle Rock - Prize'],
|
||||
'Crystal 4': ['Palace of Darkness - Prize', 'Swamp Palace - Prize', 'Thieves\' Town - Prize',
|
||||
'Skull Woods - Prize', 'Turtle Rock - Prize'],
|
||||
'Crystal 7': ['Palace of Darkness - Prize', 'Swamp Palace - Prize', 'Thieves\' Town - Prize',
|
||||
'Skull Woods - Prize', 'Turtle Rock - Prize'],
|
||||
'Crystal 5': ['Ice Palace - Prize', 'Misery Mire - Prize'],
|
||||
'Crystal 6': ['Ice Palace - Prize', 'Misery Mire - Prize'],
|
||||
'Bow': ['Eastern Palace - Big Chest'],
|
||||
'Progressive Bow': ['Eastern Palace - Big Chest', 'Pyramid Fairy - Right'],
|
||||
'Book of Mudora': ['Library'],
|
||||
'Hammer': ['Palace of Darkness - Big Chest'],
|
||||
'Hookshot': ['Swamp Palace - Big Chest'],
|
||||
'Magic Mirror': ['Old Man'],
|
||||
'Ocarina': ['Flute Spot'],
|
||||
'Ocarina (Activated)': ['Flute Spot'],
|
||||
'Pegasus Boots': ['Sahasrahla'],
|
||||
'Power Glove': ['Desert Palace - Big Chest'],
|
||||
'Cape': ["King's Tomb"],
|
||||
'Mushroom': ['Mushroom'],
|
||||
'Shovel': ['Stumpy'],
|
||||
'Lamp': ["Link's House"],
|
||||
'Magic Powder': ['Potion Shop'],
|
||||
'Moon Pearl': ['Tower of Hera - Big Chest'],
|
||||
'Cane of Somaria': ['Misery Mire - Big Chest'],
|
||||
'Fire Rod': ['Skull Woods - Big Chest'],
|
||||
'Flippers': ['King Zora'],
|
||||
'Ice Rod': ['Ice Rod Cave'],
|
||||
'Titans Mitts': ["Thieves' Town - Big Chest"],
|
||||
'Bombos': ['Bombos Tablet'],
|
||||
'Ether': ['Ether Tablet'],
|
||||
'Quake': ['Catfish'],
|
||||
'Bottle': ['Bottle Merchant', 'Kakariko Tavern', 'Purple Chest', 'Hobo'],
|
||||
'Master Sword': ['Master Sword Pedestal'],
|
||||
'Tempered Sword': ['Blacksmith'],
|
||||
'Fighter Sword': ["Link's Uncle"],
|
||||
'Golden Sword': ['Pyramid Fairy - Left'],
|
||||
'Progressive Sword': ["Link's Uncle", 'Blacksmith', 'Master Sword Pedestal', 'Pyramid Fairy - Left'],
|
||||
'Progressive Glove': ['Desert Palace - Big Chest', "Thieves' Town - Big Chest"],
|
||||
'Silver Arrows': ['Pyramid Fairy - Right'],
|
||||
'Single Arrow': ['Palace of Darkness - Dark Basement - Left'],
|
||||
'Arrows (10)': ['Chicken House', 'Mini Moldorm Cave - Far Right', 'Sewers - Secret Room - Right',
|
||||
'Paradox Cave Upper - Right', 'Mire Shed - Right', 'Ganons Tower - Hope Room - Left',
|
||||
'Ganons Tower - Compass Room - Bottom Right', 'Ganons Tower - DMs Room - Top Right',
|
||||
'Ganons Tower - Randomizer Room - Top Left', 'Ganons Tower - Randomizer Room - Top Right',
|
||||
"Ganons Tower - Bob's Chest", 'Ganons Tower - Big Key Room - Left'],
|
||||
'Bombs (3)': ['Floodgate Chest', "Sahasrahla's Hut - Middle", 'Kakariko Well - Bottom', 'Superbunny Cave - Top',
|
||||
'Mini Moldorm Cave - Far Left', 'Sewers - Secret Room - Left', 'Paradox Cave Upper - Left',
|
||||
"Thieves' Town - Attic", 'Ice Palace - Freezor Chest', 'Palace of Darkness - Dark Maze - Top',
|
||||
'Ganons Tower - Hope Room - Right', 'Ganons Tower - DMs Room - Top Left',
|
||||
'Ganons Tower - Randomizer Room - Bottom Left', 'Ganons Tower - Randomizer Room - Bottom Right',
|
||||
'Ganons Tower - Big Key Room - Right', 'Ganons Tower - Mini Helmasaur Room - Left',
|
||||
'Ganons Tower - Mini Helmasaur Room - Right'],
|
||||
'Blue Mail': ['Ice Palace - Big Chest'],
|
||||
'Red Mail': ['Ganons Tower - Big Chest'],
|
||||
'Progressive Armor': ['Ice Palace - Big Chest', 'Ganons Tower - Big Chest'],
|
||||
'Blue Boomerang': ['Hyrule Castle - Boomerang Chest'],
|
||||
'Red Boomerang': ['Waterfall Fairy - Left'],
|
||||
'Blue Shield': ['Secret Passage'],
|
||||
'Red Shield': ['Waterfall Fairy - Right'],
|
||||
'Mirror Shield': ['Turtle Rock - Big Chest'],
|
||||
'Progressive Shield': ['Secret Passage', 'Waterfall Fairy - Right', 'Turtle Rock - Big Chest'],
|
||||
'Bug Catching Net': ['Sick Kid'],
|
||||
'Cane of Byrna': ['Spike Cave'],
|
||||
'Boss Heart Container': ['Desert Palace - Boss', 'Eastern Palace - Boss', 'Tower of Hera - Boss',
|
||||
'Swamp Palace - Boss', "Thieves' Town - Boss", 'Skull Woods - Boss', 'Ice Palace - Boss',
|
||||
'Misery Mire - Boss', 'Turtle Rock - Boss', 'Palace of Darkness - Boss'],
|
||||
'Sanctuary Heart Container': ['Sanctuary'],
|
||||
'Piece of Heart': ['Sunken Treasure', "Blind's Hideout - Top", "Zora's Ledge", "Aginah's Cave", 'Maze Race',
|
||||
'Kakariko Well - Top', 'Lost Woods Hideout', 'Lumberjack Tree', 'Cave 45', 'Graveyard Cave',
|
||||
'Checkerboard Cave', 'Bonk Rock Cave', 'Lake Hylia Island', 'Desert Ledge', 'Spectacle Rock',
|
||||
'Spectacle Rock Cave', 'Pyramid', 'Digging Game', 'Peg Cave', 'Chest Game', 'Bumper Cave Ledge',
|
||||
'Mire Shed - Left', 'Floating Island', 'Mimic Cave'],
|
||||
'Rupee (1)': ['Turtle Rock - Eye Bridge - Top Right', 'Ganons Tower - Compass Room - Top Right'],
|
||||
'Rupees (5)': ["Hyrule Castle - Zelda's Chest", 'Turtle Rock - Eye Bridge - Top Left',
|
||||
# 'Palace of Darkness - Harmless Hellway',
|
||||
'Palace of Darkness - Dark Maze - Bottom',
|
||||
'Ganons Tower - Validation Chest'],
|
||||
'Rupees (20)': ["Blind's Hideout - Left", "Blind's Hideout - Right", "Blind's Hideout - Far Left",
|
||||
"Blind's Hideout - Far Right", 'Kakariko Well - Left', 'Kakariko Well - Middle',
|
||||
'Kakariko Well - Right', 'Mini Moldorm Cave - Left', 'Mini Moldorm Cave - Right',
|
||||
'Paradox Cave Lower - Far Left', 'Paradox Cave Lower - Left', 'Paradox Cave Lower - Right',
|
||||
'Paradox Cave Lower - Far Right', 'Paradox Cave Lower - Middle', 'Hype Cave - Top',
|
||||
'Hype Cave - Middle Right', 'Hype Cave - Middle Left', 'Hype Cave - Bottom',
|
||||
'Swamp Palace - West Chest', 'Swamp Palace - Flooded Room - Left', 'Swamp Palace - Waterfall Room',
|
||||
'Swamp Palace - Flooded Room - Right', "Thieves' Town - Ambush Chest",
|
||||
'Turtle Rock - Eye Bridge - Bottom Right', 'Ganons Tower - Compass Room - Bottom Left',
|
||||
'Swamp Palace - Flooded Room - Right', "Thieves' Town - Ambush Chest",
|
||||
'Ganons Tower - DMs Room - Bottom Left', 'Ganons Tower - DMs Room - Bottom Right'],
|
||||
'Rupees (50)': ["Sahasrahla's Hut - Left", "Sahasrahla's Hut - Right", 'Spiral Cave', 'Superbunny Cave - Bottom',
|
||||
'Hookshot Cave - Top Right', 'Hookshot Cave - Top Left', 'Hookshot Cave - Bottom Right',
|
||||
'Hookshot Cave - Bottom Left'],
|
||||
'Rupees (100)': ['Eastern Palace - Cannonball Chest'],
|
||||
'Rupees (300)': ['Mini Moldorm Cave - Generous Guy', 'Sewers - Secret Room - Middle', 'Hype Cave - Generous Guy',
|
||||
'Brewery', 'C-Shaped House'],
|
||||
'Magic Upgrade (1/2)': ['Magic Bat'],
|
||||
'Big Key (Eastern Palace)': ['Eastern Palace - Big Key Chest'],
|
||||
'Compass (Eastern Palace)': ['Eastern Palace - Compass Chest'],
|
||||
'Map (Eastern Palace)': ['Eastern Palace - Map Chest'],
|
||||
'Small Key (Desert Palace)': ['Desert Palace - Torch'],
|
||||
'Big Key (Desert Palace)': ['Desert Palace - Big Key Chest'],
|
||||
'Compass (Desert Palace)': ['Desert Palace - Compass Chest'],
|
||||
'Map (Desert Palace)': ['Desert Palace - Map Chest'],
|
||||
'Small Key (Tower of Hera)': ['Tower of Hera - Basement Cage'],
|
||||
'Big Key (Tower of Hera)': ['Tower of Hera - Big Key Chest'],
|
||||
'Compass (Tower of Hera)': ['Tower of Hera - Compass Chest'],
|
||||
'Map (Tower of Hera)': ['Tower of Hera - Map Chest'],
|
||||
'Small Key (Escape)': ['Sewers - Dark Cross'],
|
||||
'Map (Escape)': ['Hyrule Castle - Map Chest'],
|
||||
'Small Key (Agahnims Tower)': ['Castle Tower - Room 03', 'Castle Tower - Dark Maze'],
|
||||
'Small Key (Palace of Darkness)': ['Palace of Darkness - Shooter Room', 'Palace of Darkness - The Arena - Bridge',
|
||||
'Palace of Darkness - Stalfos Basement',
|
||||
'Palace of Darkness - The Arena - Ledge',
|
||||
'Palace of Darkness - Dark Basement - Right',
|
||||
'Palace of Darkness - Harmless Hellway'],
|
||||
# 'Palace of Darkness - Dark Maze - Bottom'],
|
||||
'Big Key (Palace of Darkness)': ['Palace of Darkness - Big Key Chest'],
|
||||
'Compass (Palace of Darkness)': ['Palace of Darkness - Compass Chest'],
|
||||
'Map (Palace of Darkness)': ['Palace of Darkness - Map Chest'],
|
||||
'Small Key (Thieves Town)': ["Thieves' Town - Blind's Cell"],
|
||||
'Big Key (Thieves Town)': ["Thieves' Town - Big Key Chest"],
|
||||
'Compass (Thieves Town)': ["Thieves' Town - Compass Chest"],
|
||||
'Map (Thieves Town)': ["Thieves' Town - Map Chest"],
|
||||
'Small Key (Skull Woods)': ['Skull Woods - Pot Prison', 'Skull Woods - Pinball Room', 'Skull Woods - Bridge Room'],
|
||||
'Big Key (Skull Woods)': ['Skull Woods - Big Key Chest'],
|
||||
'Compass (Skull Woods)': ['Skull Woods - Compass Chest'],
|
||||
'Map (Skull Woods)': ['Skull Woods - Map Chest'],
|
||||
'Small Key (Swamp Palace)': ['Swamp Palace - Entrance'],
|
||||
'Big Key (Swamp Palace)': ['Swamp Palace - Big Key Chest'],
|
||||
'Compass (Swamp Palace)': ['Swamp Palace - Compass Chest'],
|
||||
'Map (Swamp Palace)': ['Swamp Palace - Map Chest'],
|
||||
'Small Key (Ice Palace)': ['Ice Palace - Iced T Room', 'Ice Palace - Spike Room'],
|
||||
'Big Key (Ice Palace)': ['Ice Palace - Big Key Chest'],
|
||||
'Compass (Ice Palace)': ['Ice Palace - Compass Chest'],
|
||||
'Map (Ice Palace)': ['Ice Palace - Map Chest'],
|
||||
'Small Key (Misery Mire)': ['Misery Mire - Main Lobby', 'Misery Mire - Bridge Chest', 'Misery Mire - Spike Chest'],
|
||||
'Big Key (Misery Mire)': ['Misery Mire - Big Key Chest'],
|
||||
'Compass (Misery Mire)': ['Misery Mire - Compass Chest'],
|
||||
'Map (Misery Mire)': ['Misery Mire - Map Chest'],
|
||||
'Small Key (Turtle Rock)': ['Turtle Rock - Roller Room - Right', 'Turtle Rock - Chain Chomps',
|
||||
'Turtle Rock - Crystaroller Room', 'Turtle Rock - Eye Bridge - Bottom Left'],
|
||||
'Big Key (Turtle Rock)': ['Turtle Rock - Big Key Chest'],
|
||||
'Compass (Turtle Rock)': ['Turtle Rock - Compass Chest'],
|
||||
'Map (Turtle Rock)': ['Turtle Rock - Roller Room - Left'],
|
||||
'Small Key (Ganons Tower)': ["Ganons Tower - Bob's Torch", 'Ganons Tower - Tile Room',
|
||||
'Ganons Tower - Firesnake Room', 'Ganons Tower - Pre-Moldorm Chest'],
|
||||
'Big Key (Ganons Tower)': ['Ganons Tower - Big Key Chest'],
|
||||
'Compass (Ganons Tower)': ['Ganons Tower - Compass Room - Top Left'],
|
||||
'Map (Ganons Tower)': ['Ganons Tower - Map Chest']
|
||||
}
|
||||
|
||||
|
||||
keydrop_vanilla_mapping = {
|
||||
'Small Key (Eastern Palace)': ['Eastern Palace - Dark Eyegore Key Drop'],
|
||||
'Small Key (Escape)': ['Hyrule Castle - Map Guard Key Drop', 'Hyrule Castle - Boomerang Guard Key Drop',
|
||||
'Hyrule Castle - Key Rat Key Drop'],
|
||||
'Big Key (Escape)': ['Hyrule Castle - Big Key Drop'],
|
||||
'Small Key (Agahnims Tower)': ['Castle Tower - Dark Archer Key Drop', 'Castle Tower - Circle of Pots Key Drop'],
|
||||
'Small Key (Skull Woods)': ['Skull Woods - Spike Corner Key Drop'],
|
||||
'Small Key (Ice Palace)': ['Ice Palace - Jelly Key Drop', 'Ice Palace - Conveyor Key Drop'],
|
||||
'Small Key (Misery Mire)': ['Misery Mire - Conveyor Crystal Key Drop'],
|
||||
'Small Key (Turtle Rock)': ['Turtle Rock - Pokey 1 Key Drop', 'Turtle Rock - Pokey 2 Key Drop'],
|
||||
'Small Key (Ganons Tower)': ['Ganons Tower - Mini Helmasaur Key Drop'],
|
||||
}
|
||||
|
||||
potkeys_vanilla_mapping = {
|
||||
'Small Key (Desert Palace)': ['Desert Palace - Desert Tiles 1 Pot Key',
|
||||
'Desert Palace - Beamos Hall Pot Key', 'Desert Palace - Desert Tiles 2 Pot Key'],
|
||||
'Small Key (Eastern Palace)': ['Eastern Palace - Dark Square Pot Key'],
|
||||
'Small Key (Thieves Town)': ["Thieves' Town - Hallway Pot Key", "Thieves' Town - Spike Switch Pot Key"],
|
||||
'Small Key (Skull Woods)': ['Skull Woods - West Lobby Pot Key'],
|
||||
'Small Key (Swamp Palace)': ['Swamp Palace - Pot Row Pot Key', 'Swamp Palace - Trench 1 Pot Key',
|
||||
'Swamp Palace - Hookshot Pot Key', 'Swamp Palace - Trench 2 Pot Key',
|
||||
'Swamp Palace - Waterway Pot Key'],
|
||||
'Small Key (Ice Palace)': ['Ice Palace - Hammer Block Key Drop', 'Ice Palace - Many Pots Pot Key'],
|
||||
'Small Key (Misery Mire)': ['Misery Mire - Spikes Pot Key', 'Misery Mire - Fishbone Pot Key'],
|
||||
'Small Key (Ganons Tower)': ['Ganons Tower - Conveyor Cross Pot Key', 'Ganons Tower - Double Switch Pot Key',
|
||||
'Ganons Tower - Conveyor Star Pits Pot Key'],
|
||||
}
|
||||
|
||||
shop_vanilla_mapping = {
|
||||
'Red Potion': ['Dark Death Mountain Shop - Left', 'Dark Lake Hylia Shop - Left', 'Dark Lumberjack Shop - Left',
|
||||
'Village of Outcasts Shop - Left', 'Dark Potion Shop - Left', 'Paradox Shop - Left',
|
||||
'Kakariko Shop - Left', 'Lake Hylia Shop - Left', 'Potion Shop - Left'],
|
||||
'Small Heart': ['Dark Death Mountain Shop - Middle', 'Paradox Shop - Middle', 'Kakariko Shop - Middle',
|
||||
'Lake Hylia Shop - Middle'],
|
||||
'Bombs (10)': ['Dark Death Mountain Shop - Right', 'Dark Lake Hylia Shop - Right', 'Dark Lumberjack Shop - Right',
|
||||
'Village of Outcasts Shop - Right', 'Dark Potion Shop - Right', 'Paradox Shop - Right',
|
||||
'Kakariko Shop - Right', 'Lake Hylia Shop - Right'],
|
||||
'Blue Shield': ['Dark Lake Hylia Shop - Middle', 'Dark Lumberjack Shop - Middle',
|
||||
'Village of Outcasts Shop - Middle', 'Dark Potion Shop - Middle'],
|
||||
'Red Shield': ['Red Shield Shop - Left'],
|
||||
'Bee': ['Red Shield Shop - Middle'],
|
||||
'Arrows (10)': ['Red Shield Shop - Right'],
|
||||
'Bomb Upgrade (+5)': ['Capacity Upgrade - Left'],
|
||||
'Arrow Upgrade (+5)': ['Capacity Upgrade - Right'],
|
||||
'Blue Potion': ['Potion Shop - Right'],
|
||||
'Green Potion': ['Potion Shop - Middle'],
|
||||
}
|
||||
|
||||
retro_vanilla_mapping = {
|
||||
'Heart Container': ['Take-Any #1 Item 1', 'Take-Any #2 Item 1', 'Take-Any #3 Item 1', 'Take-Any #4 Item 1'],
|
||||
'Blue Potion': ['Take-Any #1 Item 2', 'Take-Any #2 Item 2', 'Take-Any #3 Item 2', 'Take-Any #4 Item 2'],
|
||||
'Progressive Sword': ['Old Man Sword Cave Item 1']
|
||||
}
|
||||
|
||||
mode_grouping = {
|
||||
'Overworld Major': [
|
||||
"Link's Uncle", 'King Zora', "Link's House", 'Sahasrahla', 'Ice Rod Cave', 'Library',
|
||||
'Master Sword Pedestal', 'Old Man', 'Ether Tablet', 'Catfish', 'Stumpy', 'Bombos Tablet', 'Mushroom',
|
||||
'Bottle Merchant', 'Kakariko Tavern', 'Secret Passage', 'Flute Spot', 'Purple Chest',
|
||||
'Waterfall Fairy - Left', 'Waterfall Fairy - Right', 'Blacksmith', 'Magic Bat', 'Sick Kid', 'Hobo',
|
||||
'Potion Shop', 'Spike Cave', 'Pyramid Fairy - Left', 'Pyramid Fairy - Right', "King's Tomb",
|
||||
],
|
||||
'Big Chests': ['Eastern Palace - Big Chest','Desert Palace - Big Chest', 'Tower of Hera - Big Chest',
|
||||
'Palace of Darkness - Big Chest', 'Swamp Palace - Big Chest', 'Skull Woods - Big Chest',
|
||||
"Thieves' Town - Big Chest", 'Misery Mire - Big Chest', 'Hyrule Castle - Boomerang Chest',
|
||||
'Ice Palace - Big Chest', 'Turtle Rock - Big Chest', 'Ganons Tower - Big Chest'],
|
||||
'Heart Containers': ['Sanctuary', 'Eastern Palace - Boss','Desert Palace - Boss', 'Tower of Hera - Boss',
|
||||
'Palace of Darkness - Boss', 'Swamp Palace - Boss', 'Skull Woods - Boss',
|
||||
"Thieves' Town - Boss", 'Ice Palace - Boss', 'Misery Mire - Boss', 'Turtle Rock - Boss'],
|
||||
'Heart Pieces': [
|
||||
'Bumper Cave Ledge', 'Desert Ledge', 'Lake Hylia Island', 'Floating Island',
|
||||
'Maze Race', 'Spectacle Rock', 'Pyramid', "Zora's Ledge", 'Lumberjack Tree',
|
||||
'Sunken Treasure', 'Spectacle Rock Cave', 'Lost Woods Hideout', 'Checkerboard Cave', 'Peg Cave', 'Cave 45',
|
||||
'Graveyard Cave', 'Kakariko Well - Top', "Blind's Hideout - Top", 'Bonk Rock Cave', "Aginah's Cave",
|
||||
'Chest Game', 'Digging Game', 'Mire Shed - Left', 'Mimic Cave'
|
||||
],
|
||||
'Big Keys': [
|
||||
'Eastern Palace - Big Key Chest', 'Ganons Tower - Big Key Chest',
|
||||
'Desert Palace - Big Key Chest', 'Tower of Hera - Big Key Chest', 'Palace of Darkness - Big Key Chest',
|
||||
'Swamp Palace - Big Key Chest', "Thieves' Town - Big Key Chest", 'Skull Woods - Big Key Chest',
|
||||
'Ice Palace - Big Key Chest', 'Misery Mire - Big Key Chest', 'Turtle Rock - Big Key Chest',
|
||||
],
|
||||
'Compasses': [
|
||||
'Eastern Palace - Compass Chest', 'Desert Palace - Compass Chest', 'Tower of Hera - Compass Chest',
|
||||
'Palace of Darkness - Compass Chest', 'Swamp Palace - Compass Chest', 'Skull Woods - Compass Chest',
|
||||
"Thieves' Town - Compass Chest", 'Ice Palace - Compass Chest', 'Misery Mire - Compass Chest',
|
||||
'Turtle Rock - Compass Chest', 'Ganons Tower - Compass Room - Top Left'
|
||||
],
|
||||
'Maps': [
|
||||
'Hyrule Castle - Map Chest', 'Eastern Palace - Map Chest', 'Desert Palace - Map Chest',
|
||||
'Tower of Hera - Map Chest', 'Palace of Darkness - Map Chest', 'Swamp Palace - Map Chest',
|
||||
'Skull Woods - Map Chest', "Thieves' Town - Map Chest", 'Ice Palace - Map Chest', 'Misery Mire - Map Chest',
|
||||
'Turtle Rock - Roller Room - Left', 'Ganons Tower - Map Chest'
|
||||
],
|
||||
'Small Keys': [
|
||||
'Sewers - Dark Cross', 'Desert Palace - Torch', 'Tower of Hera - Basement Cage',
|
||||
'Castle Tower - Room 03', 'Castle Tower - Dark Maze',
|
||||
'Palace of Darkness - Stalfos Basement', 'Palace of Darkness - Dark Basement - Right',
|
||||
'Palace of Darkness - Harmless Hellway', 'Palace of Darkness - Shooter Room',
|
||||
'Palace of Darkness - The Arena - Bridge', 'Palace of Darkness - The Arena - Ledge',
|
||||
"Thieves' Town - Blind's Cell", 'Skull Woods - Bridge Room', 'Ice Palace - Spike Room',
|
||||
'Skull Woods - Pot Prison', 'Skull Woods - Pinball Room', 'Misery Mire - Spike Chest',
|
||||
'Ice Palace - Iced T Room', 'Misery Mire - Main Lobby', 'Misery Mire - Bridge Chest', 'Swamp Palace - Entrance',
|
||||
'Turtle Rock - Chain Chomps', 'Turtle Rock - Crystaroller Room', 'Turtle Rock - Roller Room - Right',
|
||||
'Turtle Rock - Eye Bridge - Bottom Left', "Ganons Tower - Bob's Torch", 'Ganons Tower - Tile Room',
|
||||
'Ganons Tower - Firesnake Room', 'Ganons Tower - Pre-Moldorm Chest'
|
||||
],
|
||||
'Dungeon Trash': [
|
||||
'Sewers - Secret Room - Right', 'Sewers - Secret Room - Left', 'Sewers - Secret Room - Middle',
|
||||
"Hyrule Castle - Zelda's Chest", 'Eastern Palace - Cannonball Chest', "Thieves' Town - Ambush Chest",
|
||||
"Thieves' Town - Attic", 'Ice Palace - Freezor Chest', 'Palace of Darkness - Dark Basement - Left',
|
||||
'Palace of Darkness - Dark Maze - Bottom', 'Palace of Darkness - Dark Maze - Top',
|
||||
'Swamp Palace - Flooded Room - Left', 'Swamp Palace - Flooded Room - Right', 'Swamp Palace - Waterfall Room',
|
||||
'Turtle Rock - Eye Bridge - Bottom Right', 'Turtle Rock - Eye Bridge - Top Left',
|
||||
'Turtle Rock - Eye Bridge - Top Right', 'Swamp Palace - West Chest',
|
||||
],
|
||||
'Overworld Trash': [
|
||||
"Blind's Hideout - Left", "Blind's Hideout - Right", "Blind's Hideout - Far Left",
|
||||
"Blind's Hideout - Far Right", 'Kakariko Well - Left', 'Kakariko Well - Middle', 'Kakariko Well - Right',
|
||||
'Kakariko Well - Bottom', 'Chicken House', 'Floodgate Chest', 'Mini Moldorm Cave - Left',
|
||||
'Mini Moldorm Cave - Right', 'Mini Moldorm Cave - Generous Guy', 'Mini Moldorm Cave - Far Left',
|
||||
'Mini Moldorm Cave - Far Right', "Sahasrahla's Hut - Left", "Sahasrahla's Hut - Right",
|
||||
"Sahasrahla's Hut - Middle", 'Paradox Cave Lower - Far Left', 'Paradox Cave Lower - Left',
|
||||
'Paradox Cave Lower - Right', 'Paradox Cave Lower - Far Right', 'Paradox Cave Lower - Middle',
|
||||
'Paradox Cave Upper - Left', 'Paradox Cave Upper - Right', 'Spiral Cave', 'Brewery', 'C-Shaped House',
|
||||
'Hype Cave - Top', 'Hype Cave - Middle Right', 'Hype Cave - Middle Left', 'Hype Cave - Bottom',
|
||||
'Hype Cave - Generous Guy', 'Superbunny Cave - Bottom', 'Superbunny Cave - Top', 'Hookshot Cave - Top Right',
|
||||
'Hookshot Cave - Top Left', 'Hookshot Cave - Bottom Right', 'Hookshot Cave - Bottom Left', 'Mire Shed - Right'
|
||||
],
|
||||
'GT Trash': [
|
||||
'Ganons Tower - DMs Room - Top Right', 'Ganons Tower - DMs Room - Top Left',
|
||||
'Ganons Tower - DMs Room - Bottom Left', 'Ganons Tower - DMs Room - Bottom Right',
|
||||
'Ganons Tower - Compass Room - Top Right', 'Ganons Tower - Compass Room - Bottom Right',
|
||||
'Ganons Tower - Compass Room - Bottom Left', 'Ganons Tower - Hope Room - Left',
|
||||
'Ganons Tower - Hope Room - Right', 'Ganons Tower - Randomizer Room - Top Left',
|
||||
'Ganons Tower - Randomizer Room - Top Right', 'Ganons Tower - Randomizer Room - Bottom Right',
|
||||
'Ganons Tower - Randomizer Room - Bottom Left', "Ganons Tower - Bob's Chest",
|
||||
'Ganons Tower - Big Key Room - Left', 'Ganons Tower - Big Key Room - Right',
|
||||
'Ganons Tower - Mini Helmasaur Room - Left', 'Ganons Tower - Mini Helmasaur Room - Right',
|
||||
'Ganons Tower - Validation Chest',
|
||||
],
|
||||
'Key Drops': [
|
||||
'Hyrule Castle - Map Guard Key Drop', 'Hyrule Castle - Boomerang Guard Key Drop',
|
||||
'Hyrule Castle - Key Rat Key Drop', 'Eastern Palace - Dark Eyegore Key Drop',
|
||||
'Castle Tower - Dark Archer Key Drop', 'Castle Tower - Circle of Pots Key Drop',
|
||||
'Skull Woods - Spike Corner Key Drop', 'Ice Palace - Jelly Key Drop', 'Ice Palace - Conveyor Key Drop',
|
||||
'Misery Mire - Conveyor Crystal Key Drop', 'Turtle Rock - Pokey 1 Key Drop',
|
||||
'Turtle Rock - Pokey 2 Key Drop', 'Ganons Tower - Mini Helmasuar Key Drop',
|
||||
],
|
||||
'Pot Keys': [
|
||||
'Eastern Palace - Dark Square Pot Key', 'Desert Palace - Desert Tiles 1 Pot Key',
|
||||
'Desert Palace - Beamos Hall Pot Key', 'Desert Palace - Desert Tiles 2 Pot Key',
|
||||
'Swamp Palace - Pot Row Pot Key', 'Swamp Palace - Trench 1 Pot Key', 'Swamp Palace - Hookshot Pot Key',
|
||||
'Swamp Palace - Trench 2 Pot Key', 'Swamp Palace - Waterway Pot Key', 'Skull Woods - West Lobby Pot Key',
|
||||
"Thieves' Town - Hallway Pot Key", "Thieves' Town - Spike Switch Pot Key",
|
||||
'Ice Palace - Hammer Block Key Drop', 'Ice Palace - Many Pots Pot Key', 'Misery Mire - Spikes Pot Key',
|
||||
'Misery Mire - Fishbone Pot Key', 'Ganons Tower - Conveyor Cross Pot Key',
|
||||
'Ganons Tower - Double Switch Pot Key', 'Ganons Tower - Conveyor Star Pits Pot Key',
|
||||
|
||||
],
|
||||
'Big Key Drops': ['Hyrule Castle - Big Key Drop'],
|
||||
'Shops': [
|
||||
'Dark Death Mountain Shop - Left', 'Dark Death Mountain Shop - Middle', 'Dark Death Mountain Shop - Right',
|
||||
'Red Shield Shop - Left', 'Red Shield Shop - Middle', 'Red Shield Shop - Right', 'Dark Lake Hylia Shop - Left',
|
||||
'Dark Lake Hylia Shop - Middle', 'Dark Lake Hylia Shop - Right', 'Dark Lumberjack Shop - Left',
|
||||
'Dark Lumberjack Shop - Middle', 'Dark Lumberjack Shop - Right', 'Village of Outcasts Shop - Left',
|
||||
'Village of Outcasts Shop - Middle', 'Village of Outcasts Shop - Right', 'Dark Potion Shop - Left',
|
||||
'Dark Potion Shop - Middle', 'Dark Potion Shop - Right', 'Paradox Shop - Left', 'Paradox Shop - Middle',
|
||||
'Paradox Shop - Right', 'Kakariko Shop - Left', 'Kakariko Shop - Middle', 'Kakariko Shop - Right',
|
||||
'Lake Hylia Shop - Left', 'Lake Hylia Shop - Middle', 'Lake Hylia Shop - Right', 'Capacity Upgrade - Left',
|
||||
'Capacity Upgrade - Right'
|
||||
],
|
||||
'RetroShops': [
|
||||
'Old Man Sword Cave Item 1', 'Take-Any #1 Item 1', 'Take-Any #1 Item 2', 'Take-Any #2 Item 1',
|
||||
'Take-Any #2 Item 2', 'Take-Any #3 Item 1', 'Take-Any #3 Item 2','Take-Any #4 Item 1', 'Take-Any #4 Item 2'
|
||||
]
|
||||
}
|
||||
|
||||
vanilla_fallback_dungeon_set = set(mode_grouping['Dungeon Trash'] + mode_grouping['Big Keys'] +
|
||||
mode_grouping['GT Trash'] + mode_grouping['Small Keys'] +
|
||||
mode_grouping['Compasses'] + mode_grouping['Maps'] + mode_grouping['Key Drops'] +
|
||||
mode_grouping['Big Key Drops'])
|
||||
|
||||
|
||||
major_items = {'Bombos', 'Book of Mudora', 'Cane of Somaria', 'Ether', 'Fire Rod', 'Flippers', 'Ocarina', 'Hammer',
|
||||
'Hookshot', 'Ice Rod', 'Lamp', 'Cape', 'Magic Powder', 'Mushroom', 'Pegasus Boots', 'Quake', 'Shovel',
|
||||
'Bug Catching Net', 'Cane of Byrna', 'Blue Boomerang', 'Red Boomerang', 'Progressive Glove',
|
||||
'Power Glove', 'Titans Mitts', 'Bottle', 'Bottle (Red Potion)', 'Bottle (Green Potion)', 'Magic Mirror',
|
||||
'Bottle (Blue Potion)', 'Bottle (Fairy)', 'Bottle (Bee)', 'Bottle (Good Bee)', 'Magic Upgrade (1/2)',
|
||||
'Sanctuary Heart Container', 'Boss Heart Container', 'Progressive Shield', 'Ocarina (Activated)',
|
||||
'Mirror Shield', 'Progressive Armor', 'Blue Mail', 'Red Mail', 'Progressive Sword', 'Fighter Sword',
|
||||
'Master Sword', 'Tempered Sword', 'Golden Sword', 'Bow', 'Silver Arrows', 'Triforce Piece', 'Moon Pearl',
|
||||
'Progressive Bow', 'Progressive Bow (Alt)'}
|
||||
|
||||
vanilla_swords = {"Link's Uncle", 'Master Sword Pedestal', 'Blacksmith', 'Pyramid Fairy - Left'}
|
||||
|
||||
trash_items = {
|
||||
'Nothing': -1,
|
||||
'Bee Trap': 0,
|
||||
'Rupee (1)': 1, 'Rupees (5)': 1, 'Small Heart': 1, 'Bee': 1, 'Arrows (5)': 1, 'Chicken': 1, 'Single Bomb': 1,
|
||||
'Rupees (20)': 2, 'Small Magic': 2,
|
||||
'Bombs (3)': 3, 'Arrows (10)': 3, 'Bombs (10)': 3, 'Apples': 3,
|
||||
'Fairy': 4, 'Big Magic': 4, 'Red Potion': 4, 'Blue Shield': 4, 'Rupees (50)': 4, 'Rupees (100)': 4,
|
||||
'Rupees (300)': 5,
|
||||
'Piece of Heart': 17
|
||||
}
|
||||
|
||||
pot_items = {
|
||||
PotItem.Nothing: 'Nothing',
|
||||
PotItem.Bomb: 'Single Bomb',
|
||||
PotItem.FiveArrows: 'Arrows (5)', # convert to 10
|
||||
PotItem.OneRupee: 'Rupee (1)',
|
||||
PotItem.FiveRupees: 'Rupees (5)',
|
||||
PotItem.Heart: 'Small Heart',
|
||||
PotItem.BigMagic: 'Big Magic', # fast fill
|
||||
PotItem.SmallMagic: 'Small Magic',
|
||||
PotItem.Chicken: 'Chicken' # fast fill
|
||||
}
|
||||
|
||||
valid_pot_items = {y: x for x, y in pot_items.items()}
|
||||
@@ -22,7 +22,6 @@ if os.path.isdir("build") and not sys.platform.find("mac") and not sys.platform.
|
||||
subprocess.run(" ".join([f"pyinstaller {SPEC_FILE} ",
|
||||
upx_string,
|
||||
"-y ",
|
||||
"--onefile ",
|
||||
f"--distpath {DEST_DIRECTORY} ",
|
||||
]),
|
||||
shell=True)
|
||||
|
||||
@@ -22,7 +22,6 @@ if os.path.isdir("build") and not sys.platform.find("mac") and not sys.platform.
|
||||
subprocess.run(" ".join([f"pyinstaller {SPEC_FILE} ",
|
||||
upx_string,
|
||||
"-y ",
|
||||
"--onefile ",
|
||||
f"--distpath {DEST_DIRECTORY} ",
|
||||
]),
|
||||
shell=True)
|
||||
|
||||
41
source/meta/check_requirements.py
Normal file
41
source/meta/check_requirements.py
Normal file
@@ -0,0 +1,41 @@
|
||||
import importlib.util
|
||||
import webbrowser
|
||||
from tkinter import Tk, Label, Button, Frame
|
||||
|
||||
|
||||
def check_requirements(console=False):
|
||||
check_packages = {'aenum': 'aenum',
|
||||
'fast-enum': 'fast_enum',
|
||||
'python-bps-continued': 'bps',
|
||||
'colorama': 'colorama',
|
||||
'aioconsole' : 'aioconsole',
|
||||
'websockets' : 'websockets',
|
||||
'pyyaml': 'yaml'}
|
||||
missing = []
|
||||
for package, import_name in check_packages.items():
|
||||
spec = importlib.util.find_spec(import_name)
|
||||
if spec is None:
|
||||
missing.append(package)
|
||||
if len(missing) > 0:
|
||||
packages = ','.join(missing)
|
||||
if console:
|
||||
import logging
|
||||
logger = logging.getLogger('')
|
||||
logger.error('You need to install the following python packages:')
|
||||
logger.error(f'{packages}')
|
||||
logger.error('See the step about "Installing Platform-specific dependencies":')
|
||||
logger.error('https://github.com/aerinon/ALttPDoorRandomizer/blob/DoorDev/docs/BUILDING.md')
|
||||
else:
|
||||
master = Tk()
|
||||
master.title('Error')
|
||||
frame = Frame(master)
|
||||
frame.pack(expand=True, padx =50, pady=50)
|
||||
Label(frame, text='You need to install the following python packages:').pack()
|
||||
Label(frame, text=f'{packages}').pack()
|
||||
Label(frame, text='See the step about "Installing Platform-specific dependencies":').pack()
|
||||
url = 'https://github.com/aerinon/ALttPDoorRandomizer/blob/DoorDev/docs/BUILDING.md'
|
||||
link = Label(frame, fg='blue', cursor='hand2', text=url)
|
||||
link.pack()
|
||||
link.bind('<Button-1>', lambda e: webbrowser.open_new_tab(url))
|
||||
Button(master, text='Ok', command=master.destroy).pack()
|
||||
master.mainloop()
|
||||
124
source/test/MysteryTestSuite.py
Normal file
124
source/test/MysteryTestSuite.py
Normal file
@@ -0,0 +1,124 @@
|
||||
import subprocess
|
||||
import sys
|
||||
import multiprocessing
|
||||
import concurrent.futures
|
||||
import argparse
|
||||
from collections import OrderedDict
|
||||
|
||||
cpu_threads = multiprocessing.cpu_count()
|
||||
py_version = f"{sys.version_info.major}.{sys.version_info.minor}"
|
||||
|
||||
|
||||
def main(args=None):
|
||||
successes = []
|
||||
errors = []
|
||||
task_mapping = []
|
||||
tests = OrderedDict()
|
||||
|
||||
successes.append(f"Testing {args.dr} DR with {args.count} Tests" + (f" (intensity={args.tense})" if args.dr in ['basic', 'crossed'] else ""))
|
||||
print(successes[0])
|
||||
|
||||
max_attempts = args.count
|
||||
pool = concurrent.futures.ThreadPoolExecutor(max_workers=cpu_threads)
|
||||
dead_or_alive = 0
|
||||
alive = 0
|
||||
|
||||
def test(testname: str, command: str):
|
||||
tests[testname] = [command]
|
||||
basecommand = f"python3.8 Mystery.py --suppress_rom --suppress_meta"
|
||||
|
||||
def gen_seed():
|
||||
taskcommand = basecommand + " " + command
|
||||
return subprocess.run(taskcommand, capture_output=True, shell=True, text=True)
|
||||
|
||||
for x in range(1, max_attempts + 1):
|
||||
task = pool.submit(gen_seed)
|
||||
task.success = False
|
||||
task.name = testname
|
||||
task.mode = "Mystery"
|
||||
task.cmd = basecommand + " " + command
|
||||
task_mapping.append(task)
|
||||
|
||||
for i in range(0, 100):
|
||||
test("Mystery", "--weights mystery_testsuite.yml")
|
||||
|
||||
from tqdm import tqdm
|
||||
with tqdm(concurrent.futures.as_completed(task_mapping),
|
||||
total=len(task_mapping), unit="seed(s)",
|
||||
desc=f"Success rate: 0.00%") as progressbar:
|
||||
for task in progressbar:
|
||||
dead_or_alive += 1
|
||||
try:
|
||||
result = task.result()
|
||||
if result.returncode:
|
||||
errors.append([task.name, task.cmd, result.stderr])
|
||||
else:
|
||||
alive += 1
|
||||
task.success = True
|
||||
except Exception as e:
|
||||
raise e
|
||||
|
||||
progressbar.set_description(f"Success rate: {(alive/dead_or_alive)*100:.2f}% - {task.name}")
|
||||
|
||||
def get_results(testname: str):
|
||||
result = ""
|
||||
for mode in ['Mystery']:
|
||||
dead_or_alive = [task.success for task in task_mapping if task.name == testname and task.mode == mode]
|
||||
alive = [x for x in dead_or_alive if x]
|
||||
success = f"{testname} Rate: {(len(alive) / len(dead_or_alive)) * 100:.2f}%"
|
||||
successes.append(success)
|
||||
print(success)
|
||||
result += f"{(len(alive)/len(dead_or_alive))*100:.2f}%\t"
|
||||
return result.strip()
|
||||
|
||||
results = []
|
||||
for t in tests.keys():
|
||||
results.append(get_results(t))
|
||||
|
||||
for result in results:
|
||||
print(result)
|
||||
successes.append(result)
|
||||
|
||||
return successes, errors
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
successes = []
|
||||
|
||||
parser = argparse.ArgumentParser(add_help=False)
|
||||
parser.add_argument('--count', default=0, type=lambda value: max(int(value), 0))
|
||||
parser.add_argument('--cpu_threads', default=cpu_threads, type=lambda value: max(int(value), 1))
|
||||
parser.add_argument('--help', default=False, action='store_true')
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
if args.help:
|
||||
parser.print_help()
|
||||
exit(0)
|
||||
|
||||
cpu_threads = args.cpu_threads
|
||||
|
||||
for dr in [['mystery', args.count if args.count else 1, 1]]:
|
||||
|
||||
for tense in range(1, dr[2] + 1):
|
||||
args = argparse.Namespace()
|
||||
args.dr = dr[0]
|
||||
args.tense = tense
|
||||
args.count = dr[1]
|
||||
s, errors = main(args=args)
|
||||
if successes:
|
||||
successes += [""] * 2
|
||||
successes += s
|
||||
print()
|
||||
|
||||
if errors:
|
||||
with open(f"{dr[0]}{(f'-{tense}' if dr[0] in ['basic', 'crossed'] else '')}-errors.txt", 'w') as stream:
|
||||
for error in errors:
|
||||
stream.write(error[0] + "\n")
|
||||
stream.write(error[1] + "\n")
|
||||
stream.write(error[2] + "\n\n")
|
||||
|
||||
with open("success.txt", "w") as stream:
|
||||
stream.write(str.join("\n", successes))
|
||||
|
||||
input("Press enter to continue")
|
||||
0
source/test/__init__.py
Normal file
0
source/test/__init__.py
Normal file
324
source/tools/BPS.py
Normal file
324
source/tools/BPS.py
Normal file
@@ -0,0 +1,324 @@
|
||||
# Code derived from https://github.com/marcrobledo/RomPatcher.js (MIT License)
|
||||
|
||||
import sys
|
||||
|
||||
from time import perf_counter
|
||||
|
||||
from collections import defaultdict
|
||||
from binascii import crc32
|
||||
try:
|
||||
from fast_enum import FastEnum
|
||||
except ImportError:
|
||||
from enum import IntFlag as FastEnum
|
||||
|
||||
|
||||
def bps_get_vlv_len(data):
|
||||
length = 0
|
||||
while True:
|
||||
x = data & 0x7f
|
||||
data >>= 7
|
||||
if data == 0:
|
||||
length += 1
|
||||
break
|
||||
length += 1
|
||||
data -= 1
|
||||
return length
|
||||
|
||||
|
||||
def bps_read_vlv(stream):
|
||||
data, shift = 0, 1
|
||||
while True:
|
||||
x = stream.read(1)[0]
|
||||
data += (x & 0x7f) * shift
|
||||
if x & 0x80:
|
||||
return data
|
||||
shift <<= 7
|
||||
data += shift
|
||||
|
||||
|
||||
class Bps:
|
||||
def __init__(self):
|
||||
self.source_size = 0
|
||||
self.target_size = 0
|
||||
self.metadata = ''
|
||||
self.actions = []
|
||||
self.source_checksum = 0
|
||||
self.target_checksum = 0
|
||||
self.patch_checksum = 0
|
||||
|
||||
self.binary_ba = bytearray()
|
||||
self.offset = 0
|
||||
|
||||
def write_to_binary(self):
|
||||
patch_size = 4
|
||||
patch_size += bps_get_vlv_len(self.source_size)
|
||||
patch_size += bps_get_vlv_len(self.target_size)
|
||||
patch_size += bps_get_vlv_len(len(self.metadata))
|
||||
patch_size += len(self.metadata)
|
||||
|
||||
for action in self.actions:
|
||||
mode, length, data = action
|
||||
patch_size += bps_get_vlv_len(((length - 1) << 2) + mode)
|
||||
|
||||
if mode == BpsMode.BPS_ACTION_TARGET_READ:
|
||||
patch_size += length
|
||||
elif mode == BpsMode.BPS_ACTION_SOURCE_COPY or mode == BpsMode.BPS_ACTION_TARGET_COPY:
|
||||
patch_size += bps_get_vlv_len((abs(data) << 1) + (1 if data < 0 else 0))
|
||||
patch_size += 12
|
||||
|
||||
self.binary_ba = bytearray(patch_size)
|
||||
self.write_string('BPS1')
|
||||
self.bps_write_vlv(self.source_size)
|
||||
self.bps_write_vlv(self.target_size)
|
||||
self.bps_write_vlv(len(self.metadata))
|
||||
self.write_string(self.metadata)
|
||||
|
||||
for action in self.actions:
|
||||
mode, length, data = action
|
||||
self.bps_write_vlv(((length - 1) << 2) + mode)
|
||||
if mode == BpsMode.BPS_ACTION_TARGET_READ:
|
||||
self.write_bytes(data)
|
||||
elif mode == BpsMode.BPS_ACTION_SOURCE_COPY or mode == BpsMode.BPS_ACTION_TARGET_COPY:
|
||||
self.bps_write_vlv((abs(data) << 1) + (1 if data < 0 else 0))
|
||||
self.write_u32(self.source_checksum)
|
||||
self.write_u32(self.target_checksum)
|
||||
self.write_u32(self.patch_checksum)
|
||||
|
||||
def write_string(self, string):
|
||||
for ch in string:
|
||||
self.binary_ba[self.offset] = ord(ch)
|
||||
self.offset += 1
|
||||
|
||||
def write_byte(self, byte):
|
||||
self.binary_ba[self.offset] = byte
|
||||
self.offset += 1
|
||||
|
||||
def write_bytes(self, m_bytes):
|
||||
for byte in m_bytes:
|
||||
self.binary_ba[self.offset] = byte
|
||||
self.offset += 1
|
||||
|
||||
def write_u32(self, data):
|
||||
self.binary_ba[self.offset] = data & 0x000000ff
|
||||
self.binary_ba[self.offset+1] = (data & 0x0000ff00) >> 8
|
||||
self.binary_ba[self.offset+2] = (data & 0x00ff0000) >> 16
|
||||
self.binary_ba[self.offset+3] = (data & 0xff000000) >> 24
|
||||
self.offset += 4
|
||||
|
||||
def bps_write_vlv(self, data):
|
||||
while True:
|
||||
x = data & 0x7f
|
||||
data >>= 7
|
||||
if data == 0:
|
||||
self.write_byte(0x80 | x)
|
||||
break
|
||||
self.write_byte(x)
|
||||
data -= 1
|
||||
|
||||
|
||||
class BpsMode(FastEnum):
|
||||
BPS_ACTION_SOURCE_READ = 0
|
||||
BPS_ACTION_TARGET_READ = 1
|
||||
BPS_ACTION_SOURCE_COPY = 2
|
||||
BPS_ACTION_TARGET_COPY = 3
|
||||
|
||||
|
||||
def create_bps_from_data(original, modified):
|
||||
patch = Bps()
|
||||
patch.source_size = len(original)
|
||||
patch.target_size = len(modified)
|
||||
|
||||
patch.actions = create_bps_linear(original, modified)
|
||||
|
||||
patch.source_checksum = crc32(original)
|
||||
patch.target_checksum = crc32(modified)
|
||||
patch.write_to_binary()
|
||||
patch.patch_checksum = crc32(patch.binary_ba[:-4])
|
||||
patch.offset = len(patch.binary_ba) - 4
|
||||
patch.write_u32(patch.patch_checksum)
|
||||
return patch
|
||||
|
||||
|
||||
def create_bps_delta(original, modified):
|
||||
patch_actions = []
|
||||
source_data = original
|
||||
target_data = modified
|
||||
source_size = len(original)
|
||||
target_size = len(modified)
|
||||
|
||||
source_relative_offset = 0
|
||||
target_relative_offset = 0
|
||||
output_offset = 0
|
||||
|
||||
source_tree = defaultdict(list)
|
||||
source_tree_2 = defaultdict(list)
|
||||
target_tree = defaultdict(list)
|
||||
|
||||
t1_start = perf_counter()
|
||||
for offset in range(0, source_size):
|
||||
symbol = source_data[offset]
|
||||
if offset < source_size - 1:
|
||||
symbol |= source_data[offset + 1] << 8
|
||||
source_tree[symbol].append(offset)
|
||||
print(f'Elasped Time 1: {perf_counter()-t1_start}')
|
||||
|
||||
source_array = list(source_data)
|
||||
|
||||
t2_start = perf_counter()
|
||||
for offset in range(0, source_size):
|
||||
symbol = source_array[offset]
|
||||
if offset < source_size - 1:
|
||||
symbol |= source_array[offset + 1] << 8
|
||||
source_tree_2[symbol].append(offset)
|
||||
print(f'Elasped Time 2: {perf_counter()-t2_start}')
|
||||
|
||||
trl = {'target_read_length': 0}
|
||||
|
||||
def target_read_flush(buffer):
|
||||
if buffer['target_read_length']:
|
||||
action = (BpsMode.BPS_ACTION_TARGET_READ, buffer['target_read_length'], [])
|
||||
patch_actions.append(action)
|
||||
offset = output_offset - buffer['target_read_length']
|
||||
while buffer['target_read_length']:
|
||||
action[2].append(target_data[offset])
|
||||
offset += 1
|
||||
buffer['target_read_length'] -= 1
|
||||
|
||||
while output_offset < target_size:
|
||||
max_length, max_offset, mode = 0, 0, BpsMode.BPS_ACTION_TARGET_READ
|
||||
|
||||
symbol = target_data[output_offset]
|
||||
|
||||
if output_offset < target_size - 1:
|
||||
symbol |= target_data[output_offset + 1] << 8
|
||||
|
||||
# source read
|
||||
length, offset = 0, output_offset
|
||||
while offset < source_size and offset < target_size and source_data[offset] == target_data[offset]:
|
||||
length += 1
|
||||
offset += 1
|
||||
if length > max_length:
|
||||
max_length, mode = length, BpsMode.BPS_ACTION_SOURCE_READ
|
||||
|
||||
# source copy
|
||||
for node in source_tree[symbol]:
|
||||
length, x, y = 0, node, output_offset
|
||||
while x < source_size and y < target_size and source_data[x] == target_data[y]:
|
||||
length += 1
|
||||
x += 1
|
||||
y += 1
|
||||
if length > max_length:
|
||||
max_length, max_offset, mode = length, node, BpsMode.BPS_ACTION_SOURCE_COPY
|
||||
|
||||
# target copy
|
||||
for node in target_tree[symbol]:
|
||||
length, x, y = 0, node, output_offset
|
||||
while y < target_size and target_data[x] == target_data[y]:
|
||||
length += 1
|
||||
x += 1
|
||||
y += 1
|
||||
if length > max_length:
|
||||
max_length, max_offset, mode = length, node, BpsMode.BPS_ACTION_TARGET_COPY
|
||||
target_tree[symbol].append(output_offset)
|
||||
|
||||
# target read
|
||||
if max_length < 4:
|
||||
max_length = min(1, target_size - output_offset)
|
||||
mode = BpsMode.BPS_ACTION_TARGET_READ
|
||||
|
||||
if mode != BpsMode.BPS_ACTION_TARGET_READ:
|
||||
target_read_flush(trl)
|
||||
|
||||
if mode == BpsMode.BPS_ACTION_SOURCE_READ:
|
||||
patch_actions.append((mode, max_length, None))
|
||||
elif mode == BpsMode.BPS_ACTION_TARGET_READ:
|
||||
trl['target_read_length'] += max_length
|
||||
else:
|
||||
if mode == BpsMode.BPS_ACTION_SOURCE_COPY:
|
||||
relative_offset = max_offset - source_relative_offset
|
||||
source_relative_offset = max_offset + max_length
|
||||
else:
|
||||
relative_offset = max_offset - target_relative_offset
|
||||
target_relative_offset = max_offset + max_length
|
||||
patch_actions.append((mode, max_length, relative_offset))
|
||||
|
||||
output_offset += max_length
|
||||
|
||||
target_read_flush(trl)
|
||||
|
||||
return patch_actions
|
||||
|
||||
|
||||
def create_bps_linear(original, modified):
|
||||
patch_actions = []
|
||||
source_data = original
|
||||
target_data = modified
|
||||
source_size = len(original)
|
||||
target_size = len(modified)
|
||||
|
||||
target_relative_offset = 0
|
||||
output_offset = 0
|
||||
trl = {'target_read_length': 0}
|
||||
|
||||
def target_read_flush(buffer):
|
||||
if buffer['target_read_length']:
|
||||
action = (BpsMode.BPS_ACTION_TARGET_READ, buffer['target_read_length'], [])
|
||||
patch_actions.append(action)
|
||||
offset = output_offset - buffer['target_read_length']
|
||||
while buffer['target_read_length']:
|
||||
action[2].append(target_data[offset])
|
||||
offset += 1
|
||||
buffer['target_read_length'] -= 1
|
||||
|
||||
eof = min(source_size, target_size)
|
||||
while output_offset < target_size:
|
||||
src_length, n = 0, 0
|
||||
|
||||
while output_offset + n < eof:
|
||||
if source_data[output_offset + n] != target_data[output_offset + n]:
|
||||
break
|
||||
src_length += 1
|
||||
n += 1
|
||||
|
||||
rle_length, n = 0, 1
|
||||
while output_offset + n < target_size:
|
||||
if target_data[output_offset] != target_data[output_offset + n]:
|
||||
break
|
||||
rle_length += 1
|
||||
n += 1
|
||||
|
||||
if rle_length >= 4:
|
||||
trl['target_read_length'] += 1
|
||||
output_offset += 1
|
||||
target_read_flush(trl)
|
||||
|
||||
relative_offset = (output_offset - 1) - target_relative_offset
|
||||
patch_actions.append((BpsMode.BPS_ACTION_TARGET_COPY, rle_length, relative_offset))
|
||||
output_offset += rle_length
|
||||
target_relative_offset = output_offset - 1
|
||||
elif src_length >= 4:
|
||||
target_read_flush(trl)
|
||||
patch_actions.append((BpsMode.BPS_ACTION_SOURCE_READ, src_length, None))
|
||||
output_offset += src_length
|
||||
else:
|
||||
trl['target_read_length'] += 1
|
||||
output_offset += 1
|
||||
|
||||
target_read_flush(trl)
|
||||
|
||||
return patch_actions
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
with open(sys.argv[1], 'rb') as source:
|
||||
sourcedata = source.read()
|
||||
|
||||
with open(sys.argv[2], 'rb') as target:
|
||||
targetdata = target.read()
|
||||
|
||||
patch = create_bps_from_data(sourcedata, targetdata)
|
||||
with open(sys.argv[3], 'wb') as patchfile:
|
||||
patchfile.write(patch.binary_ba)
|
||||
|
||||
|
||||
|
||||
284
source/tools/Bias.py
Normal file
284
source/tools/Bias.py
Normal file
@@ -0,0 +1,284 @@
|
||||
# Demo script showing bias in VT and ER boss shuffle algorithms, with proposed fixes.
|
||||
|
||||
import random
|
||||
|
||||
boss_location_list = [
|
||||
'GT_top',
|
||||
'GT_mid',
|
||||
'TH',
|
||||
'SW',
|
||||
'EP',
|
||||
'DP',
|
||||
'PD',
|
||||
'SP',
|
||||
'TT',
|
||||
'IP',
|
||||
'MM',
|
||||
'TR',
|
||||
'GT_bot'
|
||||
]
|
||||
|
||||
entrance_rando_boss_location_list = [
|
||||
'GT_top',
|
||||
'TH',
|
||||
'SW',
|
||||
'GT_mid',
|
||||
'EP',
|
||||
'DP',
|
||||
'PD',
|
||||
'SP',
|
||||
'TT',
|
||||
'IP',
|
||||
'MM',
|
||||
'TR',
|
||||
'GT_bot'
|
||||
]
|
||||
|
||||
boss_list = [
|
||||
'Armos',
|
||||
'Lanmo',
|
||||
'Moldorm',
|
||||
'Helma',
|
||||
'Arrghus',
|
||||
'Moth',
|
||||
'Blind',
|
||||
'Khold',
|
||||
'Vitty',
|
||||
'Trinexx'
|
||||
]
|
||||
|
||||
results_dict = {
|
||||
'GT_top': {'Armos': 0, 'Lanmo': 0, 'Moldorm': 0, 'Helma': 0, 'Arrghus': 0, 'Moth': 0, 'Blind': 0, 'Khold': 0, 'Vitty': 0, 'Trinexx': 0},
|
||||
'GT_mid': {'Armos': 0, 'Lanmo': 0, 'Moldorm': 0, 'Helma': 0, 'Arrghus': 0, 'Moth': 0, 'Blind': 0, 'Khold': 0, 'Vitty': 0, 'Trinexx': 0},
|
||||
'TH': {'Armos': 0, 'Lanmo': 0, 'Moldorm': 0, 'Helma': 0, 'Arrghus': 0, 'Moth': 0, 'Blind': 0, 'Khold': 0, 'Vitty': 0, 'Trinexx': 0},
|
||||
'SW': {'Armos': 0, 'Lanmo': 0, 'Moldorm': 0, 'Helma': 0, 'Arrghus': 0, 'Moth': 0, 'Blind': 0, 'Khold': 0, 'Vitty': 0, 'Trinexx': 0},
|
||||
'EP': {'Armos': 0, 'Lanmo': 0, 'Moldorm': 0, 'Helma': 0, 'Arrghus': 0, 'Moth': 0, 'Blind': 0, 'Khold': 0, 'Vitty': 0, 'Trinexx': 0},
|
||||
'DP': {'Armos': 0, 'Lanmo': 0, 'Moldorm': 0, 'Helma': 0, 'Arrghus': 0, 'Moth': 0, 'Blind': 0, 'Khold': 0, 'Vitty': 0, 'Trinexx': 0},
|
||||
'PD': {'Armos': 0, 'Lanmo': 0, 'Moldorm': 0, 'Helma': 0, 'Arrghus': 0, 'Moth': 0, 'Blind': 0, 'Khold': 0, 'Vitty': 0, 'Trinexx': 0},
|
||||
'SP': {'Armos': 0, 'Lanmo': 0, 'Moldorm': 0, 'Helma': 0, 'Arrghus': 0, 'Moth': 0, 'Blind': 0, 'Khold': 0, 'Vitty': 0, 'Trinexx': 0},
|
||||
'TT': {'Armos': 0, 'Lanmo': 0, 'Moldorm': 0, 'Helma': 0, 'Arrghus': 0, 'Moth': 0, 'Blind': 0, 'Khold': 0, 'Vitty': 0, 'Trinexx': 0},
|
||||
'IP': {'Armos': 0, 'Lanmo': 0, 'Moldorm': 0, 'Helma': 0, 'Arrghus': 0, 'Moth': 0, 'Blind': 0, 'Khold': 0, 'Vitty': 0, 'Trinexx': 0},
|
||||
'MM': {'Armos': 0, 'Lanmo': 0, 'Moldorm': 0, 'Helma': 0, 'Arrghus': 0, 'Moth': 0, 'Blind': 0, 'Khold': 0, 'Vitty': 0, 'Trinexx': 0},
|
||||
'TR': {'Armos': 0, 'Lanmo': 0, 'Moldorm': 0, 'Helma': 0, 'Arrghus': 0, 'Moth': 0, 'Blind': 0, 'Khold': 0, 'Vitty': 0, 'Trinexx': 0},
|
||||
'GT_bot': {'Armos': 0, 'Lanmo': 0, 'Moldorm': 0, 'Helma': 0, 'Arrghus': 0, 'Moth': 0, 'Blind': 0, 'Khold': 0, 'Vitty': 0, 'Trinexx': 0}
|
||||
}
|
||||
|
||||
def can_place_boss(boss:str, dungeon_name: str) -> bool:
|
||||
if (dungeon_name == 'GT_top'):
|
||||
if boss in {'Armos', 'Arrghus', 'Blind', 'Trinexx', 'Lanmo'}:
|
||||
return False
|
||||
elif (dungeon_name == 'GT_mid'):
|
||||
if boss == 'Blind':
|
||||
return False
|
||||
elif (dungeon_name == 'TH'):
|
||||
if boss in {'Armos', 'Arrghus', 'Blind', 'Trinexx', 'Lanmo'}:
|
||||
return False
|
||||
elif (dungeon_name == 'SW'):
|
||||
if boss == 'Trinexx':
|
||||
return False
|
||||
return True
|
||||
|
||||
def resetresults():
|
||||
for boss_location in boss_location_list:
|
||||
for boss in boss_list:
|
||||
results_dict[boss_location][boss] = 0
|
||||
|
||||
def printresults(iterations):
|
||||
for boss_location in boss_location_list:
|
||||
print("Location: " + boss_location.ljust(6) + ' ', end='')
|
||||
for boss in boss_list:
|
||||
percentagestr = "{0:.2%}".format(results_dict[boss_location][boss] / iterations)
|
||||
print(boss + ": " + percentagestr.rjust(6) + ' ', end='')
|
||||
restrictive_count = results_dict[boss_location]['Armos'] + results_dict[boss_location]['Arrghus'] + results_dict[boss_location]['Blind'] + results_dict[boss_location]['Trinexx'] + results_dict[boss_location]['Lanmo']
|
||||
restrictive_pct = "{0:.2%}".format(restrictive_count / iterations)
|
||||
nonrestrictive_count = results_dict[boss_location]['Moldorm'] + results_dict[boss_location]['Helma'] + results_dict[boss_location]['Moth'] + results_dict[boss_location]['Khold'] + results_dict[boss_location]['Vitty']
|
||||
nonrestrictive_pct = "{0:.2%}".format(nonrestrictive_count / iterations)
|
||||
print("Restrictive: " + restrictive_pct.rjust(7) + " Non-restrictive: " + nonrestrictive_pct.rjust(7))
|
||||
|
||||
# Demonstration of reshuffle algorithm at:
|
||||
# https://github.com/sporchia/alttp_vt_randomizer/blob/master/app/Randomizer.php#L399
|
||||
def vt(reshuffle):
|
||||
for i in range(iterations):
|
||||
dupes = random.sample(boss_list, 3)
|
||||
temp_full_boss_list = boss_list + dupes
|
||||
shuffled_boss_list = temp_full_boss_list.copy()
|
||||
random.shuffle(shuffled_boss_list)
|
||||
for boss_location in boss_location_list:
|
||||
boss = shuffled_boss_list.pop(0)
|
||||
while (not can_place_boss(boss, boss_location)):
|
||||
shuffled_boss_list.append(boss)
|
||||
|
||||
# Proposed fix
|
||||
if reshuffle:
|
||||
random.shuffle(shuffled_boss_list)
|
||||
|
||||
boss = shuffled_boss_list.pop(0)
|
||||
results_dict[boss_location][boss] = results_dict[boss_location][boss] + 1
|
||||
|
||||
# Demonstration of reshuffle algorithm at:
|
||||
# https://github.com/KevinCathcart/ALttPEntranceRandomizer/blob/Dev/Bosses.py#L203
|
||||
def er(reshuffle):
|
||||
for i in range(iterations):
|
||||
dupes = random.sample(boss_list, 3)
|
||||
temp_full_boss_list = boss_list + dupes
|
||||
shuffled_boss_list = temp_full_boss_list.copy()
|
||||
random.shuffle(shuffled_boss_list)
|
||||
for boss_location in entrance_rando_boss_location_list:
|
||||
boss = next((b for b in shuffled_boss_list if can_place_boss(b, boss_location)), None)
|
||||
shuffled_boss_list.remove(boss)
|
||||
results_dict[boss_location][boss] = results_dict[boss_location][boss] + 1
|
||||
|
||||
# Proposed fix
|
||||
if reshuffle:
|
||||
random.shuffle(shuffled_boss_list)
|
||||
|
||||
def er_unique():
|
||||
for i in range(iterations):
|
||||
temp_full_boss_list = boss_list
|
||||
moldorm2 = random.choice([b for b in boss_list
|
||||
if b not in ["Armos", "Arrghus", "Blind", "Trinexx", "Lanmo"]])
|
||||
lanmo2 = random.choice([b for b in boss_list
|
||||
if b not in [moldorm2, "Blind"]])
|
||||
armos2 = random.choice([b for b in boss_list if b not in [moldorm2, lanmo2]])
|
||||
gt_bosses = [moldorm2, lanmo2, armos2]
|
||||
|
||||
shuffled_boss_list = temp_full_boss_list.copy()
|
||||
for boss_location in entrance_rando_boss_location_list:
|
||||
if '_' in boss_location:
|
||||
boss = random.choice([b for b in gt_bosses if can_place_boss(b, boss_location)])
|
||||
gt_bosses.remove(boss)
|
||||
else:
|
||||
boss = random.choice([b for b in shuffled_boss_list if can_place_boss(b, boss_location)])
|
||||
shuffled_boss_list.remove(boss)
|
||||
results_dict[boss_location][boss] += 1
|
||||
|
||||
def er_unique_chaos():
|
||||
for i in range(iterations):
|
||||
temp_full_boss_list = boss_list
|
||||
|
||||
shuffled_boss_list = temp_full_boss_list.copy()
|
||||
for boss_location in entrance_rando_boss_location_list:
|
||||
if '_' in boss_location:
|
||||
boss = random.choice(b for b in boss_list if can_place_boss(b, boss_location))
|
||||
else:
|
||||
boss = random.choice(b for b in shuffled_boss_list if can_place_boss(b, boss_location))
|
||||
shuffled_boss_list.remove(boss)
|
||||
results_dict[boss_location][boss] += 1
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
|
||||
iterations = 100000
|
||||
# reshuffle = False
|
||||
#
|
||||
# vt(reshuffle)
|
||||
#
|
||||
# print("Original results:")
|
||||
#
|
||||
# printresults(iterations)
|
||||
# resetresults()
|
||||
#
|
||||
# reshuffle = True
|
||||
#
|
||||
# vt(reshuffle)
|
||||
#
|
||||
# print("")
|
||||
# print("Reshuffled results:")
|
||||
#
|
||||
# printresults(iterations)
|
||||
# resetresults()
|
||||
#
|
||||
# #ER
|
||||
# reshuffle = False
|
||||
# er(reshuffle)
|
||||
#
|
||||
# print("")
|
||||
# print("ER Original results:")
|
||||
#
|
||||
# printresults(iterations)
|
||||
# resetresults()
|
||||
#
|
||||
# reshuffle = True
|
||||
# er(reshuffle)
|
||||
#
|
||||
# print("")
|
||||
# print("ER Reshuffled results:")
|
||||
#
|
||||
# printresults(iterations)
|
||||
# resetresults()
|
||||
|
||||
er_unique()
|
||||
|
||||
print("")
|
||||
print("ER Unique results:")
|
||||
|
||||
printresults(iterations)
|
||||
resetresults()
|
||||
|
||||
er_unique_chaos()
|
||||
|
||||
print("")
|
||||
print("ER Chaos results:")
|
||||
|
||||
printresults(iterations)
|
||||
|
||||
# Results:
|
||||
#Original results:
|
||||
#Location: GT_top Armos: 0.00% Lanmo: 0.00% Moldorm: 20.00% Helma: 19.89% Arrghus: 0.00% Moth: 20.01% Blind: 0.00% Khold: 20.14% Vitty: 19.96% Trinexx: 0.00% Restrictive: 0.00% Non-restrictive: 100.00%
|
||||
#Location: GT_mid Armos: 11.05% Lanmo: 11.16% Moldorm: 11.00% Helma: 11.35% Arrghus: 11.07% Moth: 11.00% Blind: 0.00% Khold: 11.01% Vitty: 11.20% Trinexx: 11.15% Restrictive: 44.43% Non-restrictive: 55.57%
|
||||
#Location: TH Armos: 0.00% Lanmo: 0.00% Moldorm: 20.06% Helma: 19.95% Arrghus: 0.00% Moth: 20.07% Blind: 0.00% Khold: 19.88% Vitty: 20.05% Trinexx: 0.00% Restrictive: 0.00% Non-restrictive: 100.00%
|
||||
#Location: SW Armos: 11.30% Lanmo: 11.04% Moldorm: 11.30% Helma: 10.95% Arrghus: 11.09% Moth: 11.04% Blind: 11.15% Khold: 11.15% Vitty: 10.98% Trinexx: 0.00% Restrictive: 44.59% Non-restrictive: 55.42%
|
||||
#Location: EP Armos: 9.89% Lanmo: 9.90% Moldorm: 10.02% Helma: 9.97% Arrghus: 10.17% Moth: 9.98% Blind: 9.95% Khold: 10.16% Vitty: 9.90% Trinexx: 10.07% Restrictive: 49.97% Non-restrictive: 50.03%
|
||||
#Location: DP Armos: 9.98% Lanmo: 10.09% Moldorm: 9.97% Helma: 9.95% Arrghus: 10.17% Moth: 10.01% Blind: 9.87% Khold: 10.02% Vitty: 9.88% Trinexx: 10.04% Restrictive: 50.16% Non-restrictive: 49.84%
|
||||
#Location: PD Armos: 10.12% Lanmo: 9.96% Moldorm: 9.85% Helma: 9.90% Arrghus: 10.31% Moth: 9.91% Blind: 9.96% Khold: 9.87% Vitty: 9.94% Trinexx: 10.17% Restrictive: 50.52% Non-restrictive: 49.48%
|
||||
#Location: SP Armos: 10.41% Lanmo: 10.35% Moldorm: 9.61% Helma: 9.66% Arrghus: 10.29% Moth: 9.60% Blind: 10.47% Khold: 9.67% Vitty: 9.53% Trinexx: 10.42% Restrictive: 51.93% Non-restrictive: 48.07%
|
||||
#Location: TT Armos: 11.04% Lanmo: 10.98% Moldorm: 8.86% Helma: 8.95% Arrghus: 10.98% Moth: 9.12% Blind: 11.18% Khold: 8.98% Vitty: 9.03% Trinexx: 10.89% Restrictive: 55.06% Non-restrictive: 44.94%
|
||||
#Location: IP Armos: 12.10% Lanmo: 11.89% Moldorm: 7.81% Helma: 7.83% Arrghus: 11.94% Moth: 7.89% Blind: 12.84% Khold: 7.87% Vitty: 7.99% Trinexx: 11.84% Restrictive: 60.62% Non-restrictive: 39.38%
|
||||
#Location: MM Armos: 13.67% Lanmo: 13.66% Moldorm: 6.00% Helma: 6.17% Arrghus: 13.48% Moth: 6.18% Blind: 15.06% Khold: 6.25% Vitty: 6.14% Trinexx: 13.41% Restrictive: 69.28% Non-restrictive: 30.72%
|
||||
#Location: TR Armos: 15.35% Lanmo: 15.49% Moldorm: 3.87% Helma: 3.90% Arrghus: 15.31% Moth: 3.78% Blind: 19.06% Khold: 3.89% Vitty: 3.82% Trinexx: 15.53% Restrictive: 80.74% Non-restrictive: 19.26%
|
||||
#Location: GT_bot Armos: 15.24% Lanmo: 15.24% Moldorm: 1.54% Helma: 1.51% Arrghus: 15.22% Moth: 1.52% Blind: 20.44% Khold: 1.51% Vitty: 1.53% Trinexx: 26.25% Restrictive: 92.38% Non-restrictive: 7.62%
|
||||
#
|
||||
#Reshuffled results:
|
||||
#Location: GT_top Armos: 0.00% Lanmo: 0.00% Moldorm: 20.02% Helma: 19.97% Arrghus: 0.00% Moth: 20.02% Blind: 0.00% Khold: 20.07% Vitty: 19.92% Trinexx: 0.00% Restrictive: 0.00% Non-restrictive: 100.00%
|
||||
#Location: GT_mid Armos: 12.23% Lanmo: 12.05% Moldorm: 10.23% Helma: 10.24% Arrghus: 12.11% Moth: 10.31% Blind: 0.00% Khold: 10.36% Vitty: 10.33% Trinexx: 12.13% Restrictive: 48.52% Non-restrictive: 51.48%
|
||||
#Location: TH Armos: 0.00% Lanmo: 0.00% Moldorm: 19.86% Helma: 20.09% Arrghus: 0.00% Moth: 20.23% Blind: 0.00% Khold: 19.83% Vitty: 19.99% Trinexx: 0.00% Restrictive: 0.00% Non-restrictive: 100.00%
|
||||
#Location: SW Armos: 13.23% Lanmo: 13.34% Moldorm: 9.05% Helma: 9.02% Arrghus: 13.37% Moth: 9.10% Blind: 14.97% Khold: 8.98% Vitty: 8.93% Trinexx: 0.00% Restrictive: 54.92% Non-restrictive: 45.08%
|
||||
#Location: EP Armos: 11.54% Lanmo: 11.50% Moldorm: 7.96% Helma: 7.82% Arrghus: 11.76% Moth: 7.81% Blind: 12.84% Khold: 7.79% Vitty: 8.03% Trinexx: 12.95% Restrictive: 60.59% Non-restrictive: 39.41%
|
||||
#Location: DP Armos: 11.55% Lanmo: 11.49% Moldorm: 7.93% Helma: 7.85% Arrghus: 11.72% Moth: 7.69% Blind: 12.80% Khold: 7.99% Vitty: 7.74% Trinexx: 13.25% Restrictive: 60.80% Non-restrictive: 39.20%
|
||||
#Location: PD Armos: 11.79% Lanmo: 11.47% Moldorm: 7.81% Helma: 7.78% Arrghus: 11.61% Moth: 7.91% Blind: 12.81% Khold: 7.78% Vitty: 8.00% Trinexx: 13.02% Restrictive: 60.71% Non-restrictive: 39.29%
|
||||
#Location: SP Armos: 11.59% Lanmo: 11.56% Moldorm: 7.90% Helma: 7.75% Arrghus: 11.52% Moth: 8.02% Blind: 12.67% Khold: 7.92% Vitty: 7.85% Trinexx: 13.23% Restrictive: 60.57% Non-restrictive: 39.43%
|
||||
#Location: TT Armos: 11.64% Lanmo: 11.64% Moldorm: 7.77% Helma: 7.73% Arrghus: 11.70% Moth: 7.67% Blind: 12.89% Khold: 8.05% Vitty: 7.85% Trinexx: 13.07% Restrictive: 60.93% Non-restrictive: 39.07%
|
||||
#Location: IP Armos: 11.54% Lanmo: 11.77% Moldorm: 7.95% Helma: 7.97% Arrghus: 11.49% Moth: 7.80% Blind: 12.64% Khold: 7.87% Vitty: 7.89% Trinexx: 13.10% Restrictive: 60.54% Non-restrictive: 39.47%
|
||||
#Location: MM Armos: 11.69% Lanmo: 11.65% Moldorm: 7.91% Helma: 7.88% Arrghus: 11.59% Moth: 7.78% Blind: 12.80% Khold: 7.73% Vitty: 7.92% Trinexx: 13.04% Restrictive: 60.77% Non-restrictive: 39.23%
|
||||
#Location: TR Armos: 11.58% Lanmo: 11.82% Moldorm: 7.75% Helma: 7.83% Arrghus: 11.62% Moth: 7.79% Blind: 12.76% Khold: 7.90% Vitty: 7.76% Trinexx: 13.19% Restrictive: 60.97% Non-restrictive: 39.03%
|
||||
#Location: GT_bot Armos: 11.54% Lanmo: 11.60% Moldorm: 7.91% Helma: 7.87% Arrghus: 11.62% Moth: 7.90% Blind: 12.78% Khold: 7.82% Vitty: 7.86% Trinexx: 13.10% Restrictive: 60.65% Non-restrictive: 39.35%
|
||||
#
|
||||
#ER Original results:
|
||||
#Location: GT_top Armos: 0.00% Lanmo: 0.00% Moldorm: 19.94% Helma: 19.87% Arrghus: 0.00% Moth: 19.98% Blind: 0.00% Khold: 20.31% Vitty: 19.90% Trinexx: 0.00% Restrictive: 0.00% Non-restrictive: 100.00%
|
||||
#Location: GT_mid Armos: 14.15% Lanmo: 14.33% Moldorm: 4.51% Helma: 4.53% Arrghus: 14.19% Moth: 4.56% Blind: 0.00% Khold: 4.61% Vitty: 4.49% Trinexx: 34.62% Restrictive: 77.30% Non-restrictive: 22.70%
|
||||
#Location: TH Armos: 0.00% Lanmo: 0.00% Moldorm: 19.93% Helma: 19.98% Arrghus: 0.00% Moth: 20.10% Blind: 0.00% Khold: 19.75% Vitty: 20.24% Trinexx: 0.00% Restrictive: 0.00% Non-restrictive: 100.00%
|
||||
#Location: SW Armos: 21.52% Lanmo: 21.56% Moldorm: 2.76% Helma: 2.80% Arrghus: 21.50% Moth: 2.82% Blind: 21.48% Khold: 2.81% Vitty: 2.75% Trinexx: 0.00% Restrictive: 86.05% Non-restrictive: 13.95%
|
||||
#Location: EP Armos: 11.39% Lanmo: 11.33% Moldorm: 5.89% Helma: 5.69% Arrghus: 11.32% Moth: 5.83% Blind: 24.71% Khold: 5.82% Vitty: 5.76% Trinexx: 12.27% Restrictive: 71.01% Non-restrictive: 28.99%
|
||||
#Location: DP Armos: 11.62% Lanmo: 11.66% Moldorm: 8.05% Helma: 8.32% Arrghus: 11.75% Moth: 8.13% Blind: 12.46% Khold: 8.10% Vitty: 8.18% Trinexx: 11.73% Restrictive: 59.22% Non-restrictive: 40.78%
|
||||
#Location: PD Armos: 11.01% Lanmo: 10.77% Moldorm: 9.18% Helma: 9.20% Arrghus: 10.89% Moth: 9.03% Blind: 10.83% Khold: 9.13% Vitty: 9.17% Trinexx: 10.79% Restrictive: 54.29% Non-restrictive: 45.71%
|
||||
#Location: SP Armos: 10.26% Lanmo: 10.17% Moldorm: 9.73% Helma: 9.61% Arrghus: 10.33% Moth: 9.69% Blind: 10.31% Khold: 9.84% Vitty: 9.61% Trinexx: 10.45% Restrictive: 51.52% Non-restrictive: 48.48%
|
||||
#Location: TT Armos: 10.12% Lanmo: 10.01% Moldorm: 10.07% Helma: 9.97% Arrghus: 10.16% Moth: 9.92% Blind: 9.95% Khold: 9.73% Vitty: 9.96% Trinexx: 10.11% Restrictive: 50.35% Non-restrictive: 49.65%
|
||||
#Location: IP Armos: 10.03% Lanmo: 10.01% Moldorm: 10.00% Helma: 10.10% Arrghus: 9.89% Moth: 9.93% Blind: 10.13% Khold: 9.99% Vitty: 9.80% Trinexx: 10.13% Restrictive: 50.18% Non-restrictive: 49.82%
|
||||
#Location: MM Armos: 9.89% Lanmo: 9.99% Moldorm: 10.09% Helma: 10.10% Arrghus: 10.02% Moth: 9.93% Blind: 10.03% Khold: 10.00% Vitty: 10.06% Trinexx: 9.89% Restrictive: 49.81% Non-restrictive: 50.19%
|
||||
#Location: TR Armos: 10.03% Lanmo: 10.10% Moldorm: 10.01% Helma: 9.92% Arrghus: 9.94% Moth: 10.01% Blind: 10.04% Khold: 9.95% Vitty: 9.92% Trinexx: 10.08% Restrictive: 50.19% Non-restrictive: 49.81%
|
||||
#Location: GT_bot Armos: 9.90% Lanmo: 10.04% Moldorm: 9.88% Helma: 9.85% Arrghus: 10.00% Moth: 10.04% Blind: 10.09% Khold: 10.09% Vitty: 10.11% Trinexx: 10.01% Restrictive: 50.03% Non-restrictive: 49.97%
|
||||
#
|
||||
#ER Reshuffled results:
|
||||
#Location: GT_top Armos: 0.00% Lanmo: 0.00% Moldorm: 20.11% Helma: 19.81% Arrghus: 0.00% Moth: 20.22% Blind: 0.00% Khold: 20.08% Vitty: 19.78% Trinexx: 0.00% Restrictive: 0.00% Non-restrictive: 100.00%
|
||||
#Location: GT_mid Armos: 13.17% Lanmo: 13.37% Moldorm: 9.05% Helma: 9.14% Arrghus: 13.23% Moth: 9.29% Blind: 0.00% Khold: 8.96% Vitty: 9.11% Trinexx: 14.69% Restrictive: 54.45% Non-restrictive: 45.55%
|
||||
#Location: TH Armos: 0.00% Lanmo: 0.00% Moldorm: 20.03% Helma: 19.72% Arrghus: 0.00% Moth: 20.17% Blind: 0.00% Khold: 19.86% Vitty: 20.23% Trinexx: 0.00% Restrictive: 0.00% Non-restrictive: 100.00%
|
||||
#Location: SW Armos: 13.38% Lanmo: 13.41% Moldorm: 9.27% Helma: 9.33% Arrghus: 13.41% Moth: 9.27% Blind: 13.48% Khold: 9.28% Vitty: 9.17% Trinexx: 0.00% Restrictive: 53.69% Non-restrictive: 46.31%
|
||||
#Location: EP Armos: 11.50% Lanmo: 11.56% Moldorm: 7.90% Helma: 8.06% Arrghus: 11.58% Moth: 8.11% Blind: 12.72% Khold: 7.93% Vitty: 7.93% Trinexx: 12.70% Restrictive: 60.06% Non-restrictive: 39.94%
|
||||
#Location: DP Armos: 11.66% Lanmo: 11.51% Moldorm: 8.05% Helma: 7.96% Arrghus: 11.43% Moth: 7.83% Blind: 12.94% Khold: 7.89% Vitty: 7.88% Trinexx: 12.87% Restrictive: 60.40% Non-restrictive: 39.60%
|
||||
#Location: PD Armos: 11.36% Lanmo: 11.53% Moldorm: 7.89% Helma: 7.96% Arrghus: 11.46% Moth: 7.72% Blind: 13.12% Khold: 8.07% Vitty: 8.02% Trinexx: 12.87% Restrictive: 60.34% Non-restrictive: 39.66%
|
||||
#Location: SP Armos: 11.27% Lanmo: 11.38% Moldorm: 7.89% Helma: 7.98% Arrghus: 11.56% Moth: 7.89% Blind: 13.10% Khold: 8.17% Vitty: 8.08% Trinexx: 12.68% Restrictive: 59.99% Non-restrictive: 40.01%
|
||||
#Location: TT Armos: 11.32% Lanmo: 11.49% Moldorm: 7.91% Helma: 8.16% Arrghus: 11.44% Moth: 7.97% Blind: 12.93% Khold: 8.05% Vitty: 7.97% Trinexx: 12.77% Restrictive: 59.94% Non-restrictive: 40.06%
|
||||
#Location: IP Armos: 11.69% Lanmo: 11.74% Moldorm: 7.91% Helma: 8.00% Arrghus: 11.34% Moth: 7.87% Blind: 12.80% Khold: 7.82% Vitty: 7.95% Trinexx: 12.88% Restrictive: 60.44% Non-restrictive: 39.56%
|
||||
#Location: MM Armos: 11.57% Lanmo: 11.31% Moldorm: 8.13% Helma: 7.90% Arrghus: 11.32% Moth: 7.91% Blind: 13.15% Khold: 7.97% Vitty: 7.82% Trinexx: 12.91% Restrictive: 60.26% Non-restrictive: 39.74%
|
||||
#Location: TR Armos: 11.44% Lanmo: 11.30% Moldorm: 8.04% Helma: 8.13% Arrghus: 11.47% Moth: 7.90% Blind: 12.82% Khold: 8.09% Vitty: 7.96% Trinexx: 12.84% Restrictive: 59.87% Non-restrictive: 40.13%
|
||||
#Location: GT_bot Armos: 11.36% Lanmo: 11.53% Moldorm: 7.87% Helma: 7.99% Arrghus: 11.49% Moth: 7.96% Blind: 13.01% Khold: 7.98% Vitty: 8.00% Trinexx: 12.80% Restrictive: 60.19% Non-restrictive: 39.81
|
||||
0
source/tools/__init__.py
Normal file
0
source/tools/__init__.py
Normal file
Reference in New Issue
Block a user