forked from mirror/Archipelago
Compare commits
34 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
97d6e80556 | ||
|
|
d5abadc6d0 | ||
|
|
d08d716966 | ||
|
|
3ee4be2e33 | ||
|
|
9172cc4925 | ||
|
|
7f03a86dee | ||
|
|
1603bab1da | ||
|
|
70aae514be | ||
|
|
5fa1185d6d | ||
|
|
3a2a584ad3 | ||
|
|
c42f53d64f | ||
|
|
450e0eacf4 | ||
|
|
aa40e811f1 | ||
|
|
af96f71190 | ||
|
|
9e4cb6ee33 | ||
|
|
5d0748983b | ||
|
|
c4981e4b91 | ||
|
|
3f36c436ad | ||
|
|
db456cbcf1 | ||
|
|
c0b8384319 | ||
|
|
13036539b7 | ||
|
|
5a2e477dba | ||
|
|
f003c7130f | ||
|
|
0558351a12 | ||
|
|
3f20bdaaa2 | ||
|
|
3bf367d630 | ||
|
|
706fc19ab4 | ||
|
|
4fe024041d | ||
|
|
7afbf8b45b | ||
|
|
e1fc44f4e0 | ||
|
|
21fbb545e8 | ||
|
|
6cd08ea8dc | ||
|
|
85efee1432 | ||
|
|
ba9974fe2a |
@@ -20,7 +20,7 @@ from urllib.parse import urlparse
|
||||
from urllib.request import urlopen
|
||||
|
||||
from worlds.alttp.Rom import Sprite, LocalRom, apply_rom_settings, get_base_rom_bytes
|
||||
from Utils import output_path, local_path, open_file
|
||||
from Utils import output_path, local_path, open_file, get_cert_none_ssl_context, persistent_store
|
||||
|
||||
|
||||
class AdjusterWorld(object):
|
||||
@@ -102,14 +102,15 @@ def main():
|
||||
parser.add_argument('--update_sprites', action='store_true', help='Update Sprite Database, then exit.')
|
||||
args = parser.parse_args()
|
||||
args.music = not args.disablemusic
|
||||
if args.update_sprites:
|
||||
run_sprite_update()
|
||||
sys.exit()
|
||||
# set up logger
|
||||
loglevel = {'error': logging.ERROR, 'info': logging.INFO, 'warning': logging.WARNING, 'debug': logging.DEBUG}[
|
||||
args.loglevel]
|
||||
logging.basicConfig(format='%(message)s', level=loglevel)
|
||||
|
||||
if args.update_sprites:
|
||||
run_sprite_update()
|
||||
sys.exit()
|
||||
|
||||
if not os.path.isfile(args.rom):
|
||||
adjustGUI()
|
||||
else:
|
||||
@@ -118,7 +119,6 @@ def main():
|
||||
sys.exit(1)
|
||||
|
||||
args, path = adjust(args=args)
|
||||
from Utils import persistent_store
|
||||
if isinstance(args.sprite, Sprite):
|
||||
args.sprite = args.sprite.name
|
||||
persistent_store("adjuster", "last_settings_3", args)
|
||||
@@ -224,7 +224,6 @@ def adjustGUI():
|
||||
messagebox.showerror(title="Error while adjusting Rom", message=str(e))
|
||||
else:
|
||||
messagebox.showinfo(title="Success", message=f"Rom patched successfully to {path}")
|
||||
from Utils import persistent_store
|
||||
if isinstance(guiargs.sprite, Sprite):
|
||||
guiargs.sprite = guiargs.sprite.name
|
||||
persistent_store("adjuster", "last_settings_3", guiargs)
|
||||
@@ -241,12 +240,16 @@ def adjustGUI():
|
||||
def run_sprite_update():
|
||||
import threading
|
||||
done = threading.Event()
|
||||
top = Tk()
|
||||
top.withdraw()
|
||||
BackgroundTaskProgress(top, update_sprites, "Updating Sprites", lambda succesful, resultmessage: done.set())
|
||||
try:
|
||||
top = Tk()
|
||||
except:
|
||||
task = BackgroundTaskProgressNullWindow(update_sprites, lambda successful, resultmessage: done.set())
|
||||
else:
|
||||
top.withdraw()
|
||||
task = BackgroundTaskProgress(top, update_sprites, "Updating Sprites", lambda succesful, resultmessage: done.set())
|
||||
while not done.isSet():
|
||||
top.update()
|
||||
print("Done updating sprites")
|
||||
task.do_events()
|
||||
logging.info("Done updating sprites")
|
||||
|
||||
|
||||
def update_sprites(task, on_finish=None):
|
||||
@@ -254,7 +257,7 @@ def update_sprites(task, on_finish=None):
|
||||
successful = True
|
||||
sprite_dir = local_path("data", "sprites", "alttpr")
|
||||
os.makedirs(sprite_dir, exist_ok=True)
|
||||
|
||||
ctx = get_cert_none_ssl_context()
|
||||
def finished():
|
||||
task.close_window()
|
||||
if on_finish:
|
||||
@@ -262,7 +265,7 @@ def update_sprites(task, on_finish=None):
|
||||
|
||||
try:
|
||||
task.update_status("Downloading alttpr sprites list")
|
||||
with urlopen('https://alttpr.com/sprites') as response:
|
||||
with urlopen('https://alttpr.com/sprites', context=ctx) as response:
|
||||
sprites_arr = json.loads(response.read().decode("utf-8"))
|
||||
except Exception as e:
|
||||
resultmessage = "Error getting list of alttpr sprites. Sprites not updated.\n\n%s: %s" % (type(e).__name__, e)
|
||||
@@ -289,7 +292,7 @@ def update_sprites(task, on_finish=None):
|
||||
|
||||
def dl(sprite_url, filename):
|
||||
target = os.path.join(sprite_dir, filename)
|
||||
with urlopen(sprite_url) as response, open(target, 'wb') as out:
|
||||
with urlopen(sprite_url, context=ctx) as response, open(target, 'wb') as out:
|
||||
shutil.copyfileobj(response, out)
|
||||
|
||||
def rem(sprite):
|
||||
@@ -400,12 +403,39 @@ class BackgroundTaskProgress(BackgroundTask):
|
||||
def update_status(self, text):
|
||||
self.queue_event(lambda: self.label_var.set(text))
|
||||
|
||||
def do_events(self):
|
||||
self.parent.update()
|
||||
|
||||
# only call this in an event callback
|
||||
def close_window(self):
|
||||
self.stop()
|
||||
self.window.destroy()
|
||||
|
||||
|
||||
class BackgroundTaskProgressNullWindow(BackgroundTask):
|
||||
def __init__(self, code_to_run, *args):
|
||||
super().__init__(None, code_to_run, *args)
|
||||
|
||||
def process_queue(self):
|
||||
try:
|
||||
while True:
|
||||
if not self.running:
|
||||
return
|
||||
event = self.queue.get_nowait()
|
||||
event()
|
||||
except queue.Empty:
|
||||
pass
|
||||
|
||||
def do_events(self):
|
||||
self.process_queue()
|
||||
|
||||
def update_status(self, text):
|
||||
self.queue_event(lambda: logging.info(text))
|
||||
|
||||
def close_window(self):
|
||||
self.stop()
|
||||
|
||||
|
||||
def get_rom_frame(parent=None):
|
||||
romFrame = Frame(parent)
|
||||
baseRomLabel = Label(romFrame, text='LttP Base Rom: ')
|
||||
|
||||
@@ -15,7 +15,7 @@ atexit.register(input, "Press enter to exit.")
|
||||
|
||||
# 1 or more digits followed by m or g, then optional b
|
||||
max_heap_re = re.compile(r"^\d+[mMgG][bB]?$")
|
||||
forge_version = "1.17.1-37.0.109"
|
||||
forge_version = "1.17.1-37.1.1"
|
||||
|
||||
|
||||
def prompt_yes_no(prompt):
|
||||
@@ -35,12 +35,10 @@ def prompt_yes_no(prompt):
|
||||
def find_ap_randomizer_jar(forge_dir):
|
||||
mods_dir = os.path.join(forge_dir, 'mods')
|
||||
if os.path.isdir(mods_dir):
|
||||
ap_mod_re = re.compile(r"^aprandomizer-[\d\.]+\.jar$")
|
||||
for entry in os.scandir(mods_dir):
|
||||
match = ap_mod_re.match(entry.name)
|
||||
if match:
|
||||
logging.info(f"Found AP randomizer mod: {match.group()}")
|
||||
return match.group()
|
||||
if entry.name.startswith("aprandomizer") and entry.name.endswith(".jar"):
|
||||
logging.info(f"Found AP randomizer mod: {entry.name}")
|
||||
return entry.name
|
||||
return None
|
||||
else:
|
||||
os.mkdir(mods_dir)
|
||||
|
||||
@@ -13,6 +13,7 @@ Currently, the following games are supported:
|
||||
* Timespinner
|
||||
* Super Metroid
|
||||
* Secret of Evermore
|
||||
* Final Fantasy
|
||||
|
||||
For setup and instructions check out our [tutorials page](https://archipelago.gg/tutorial/).
|
||||
Downloads can be found at [Releases](https://github.com/ArchipelagoMW/Archipelago/releases), including compiled
|
||||
|
||||
@@ -1048,6 +1048,7 @@ async def game_watcher(ctx: Context):
|
||||
ctx.location_name_getter(item.location), itemOutPtr, len(ctx.items_received)))
|
||||
await snes_flush_writes(ctx)
|
||||
|
||||
|
||||
async def run_game(romfile):
|
||||
auto_start = Utils.get_options()["lttp_options"].get("rom_start", True)
|
||||
if auto_start is True:
|
||||
|
||||
2
Utils.py
2
Utils.py
@@ -23,7 +23,7 @@ class Version(typing.NamedTuple):
|
||||
build: int
|
||||
|
||||
|
||||
__version__ = "0.2.1"
|
||||
__version__ = "0.2.2"
|
||||
version_tuple = tuplize_version(__version__)
|
||||
|
||||
from yaml import load, dump, safe_load
|
||||
|
||||
@@ -14,7 +14,7 @@ from WebHostLib import app as raw_app
|
||||
from waitress import serve
|
||||
|
||||
from WebHostLib.models import db
|
||||
from WebHostLib.autolauncher import autohost
|
||||
from WebHostLib.autolauncher import autohost, autogen
|
||||
from WebHostLib.lttpsprites import update_sprites_lttp
|
||||
from WebHostLib.options import create as create_options_files
|
||||
|
||||
@@ -45,6 +45,8 @@ if __name__ == "__main__":
|
||||
create_options_files()
|
||||
if app.config["SELFLAUNCH"]:
|
||||
autohost(app.config)
|
||||
if app.config["SELFGEN"]:
|
||||
autogen(app.config)
|
||||
if app.config["SELFHOST"]: # using WSGI, you just want to run get_app()
|
||||
if app.config["DEBUG"]:
|
||||
autohost(app.config)
|
||||
|
||||
@@ -22,9 +22,10 @@ Pony(app)
|
||||
app.jinja_env.filters['any'] = any
|
||||
app.jinja_env.filters['all'] = all
|
||||
|
||||
app.config["SELFHOST"] = True
|
||||
app.config["SELFHOST"] = True # application process is in charge of running the websites
|
||||
app.config["GENERATORS"] = 8 # maximum concurrent world gens
|
||||
app.config["SELFLAUNCH"] = True
|
||||
app.config["SELFLAUNCH"] = True # application process is in charge of launching Rooms.
|
||||
app.config["SELFGEN"] = True # application process is in charge of scheduling Generations.
|
||||
app.config["DEBUG"] = False
|
||||
app.config["PORT"] = 80
|
||||
app.config['UPLOAD_FOLDER'] = UPLOAD_FOLDER
|
||||
@@ -166,8 +167,9 @@ def host_room(room: UUID):
|
||||
if request.method == "POST":
|
||||
if room.owner == session["_id"]:
|
||||
cmd = request.form["cmd"]
|
||||
Command(room=room, commandtext=cmd)
|
||||
commit()
|
||||
if cmd:
|
||||
Command(room=room, commandtext=cmd)
|
||||
commit()
|
||||
|
||||
with db_session:
|
||||
room.last_activity = datetime.utcnow() # will trigger a spinup, if it's not already running
|
||||
|
||||
@@ -110,6 +110,26 @@ def autohost(config: dict):
|
||||
def keep_running():
|
||||
try:
|
||||
with Locker("autohost"):
|
||||
while 1:
|
||||
time.sleep(0.1)
|
||||
with db_session:
|
||||
rooms = select(
|
||||
room for room in Room if
|
||||
room.last_activity >= datetime.utcnow() - timedelta(days=3))
|
||||
for room in rooms:
|
||||
launch_room(room, config)
|
||||
|
||||
except AlreadyRunningException:
|
||||
logging.info("Autohost reports as already running, not starting another.")
|
||||
|
||||
import threading
|
||||
threading.Thread(target=keep_running, name="AP_Autohost").start()
|
||||
|
||||
|
||||
def autogen(config: dict):
|
||||
def keep_running():
|
||||
try:
|
||||
with Locker("autogen"):
|
||||
|
||||
with multiprocessing.Pool(config["GENERATORS"], initializer=init_db,
|
||||
initargs=(config["PONY"],)) as generator_pool:
|
||||
@@ -129,22 +149,17 @@ def autohost(config: dict):
|
||||
select(generation for generation in Generation if generation.state == STATE_ERROR).delete()
|
||||
|
||||
while 1:
|
||||
time.sleep(0.50)
|
||||
time.sleep(0.1)
|
||||
with db_session:
|
||||
rooms = select(
|
||||
room for room in Room if
|
||||
room.last_activity >= datetime.utcnow() - timedelta(days=3))
|
||||
for room in rooms:
|
||||
launch_room(room, config)
|
||||
to_start = select(
|
||||
generation for generation in Generation if generation.state == STATE_QUEUED)
|
||||
for generation in to_start:
|
||||
launch_generator(generator_pool, generation)
|
||||
except AlreadyRunningException:
|
||||
pass
|
||||
logging.info("Autogen reports as already running, not starting another.")
|
||||
|
||||
import threading
|
||||
threading.Thread(target=keep_running).start()
|
||||
threading.Thread(target=keep_running, name="AP_Autogen").start()
|
||||
|
||||
|
||||
multiworlds = {}
|
||||
|
||||
@@ -10,6 +10,7 @@ def update_sprites_lttp():
|
||||
from tkinter import Tk
|
||||
from LttPAdjuster import get_image_for_sprite
|
||||
from LttPAdjuster import BackgroundTaskProgress
|
||||
from LttPAdjuster import BackgroundTaskProgressNullWindow
|
||||
from LttPAdjuster import update_sprites
|
||||
|
||||
# Target directories
|
||||
@@ -19,11 +20,15 @@ def update_sprites_lttp():
|
||||
os.makedirs(os.path.join(output_dir, "sprites"), exist_ok=True)
|
||||
# update sprites through gui.py's functions
|
||||
done = threading.Event()
|
||||
top = Tk()
|
||||
top.withdraw()
|
||||
BackgroundTaskProgress(top, update_sprites, "Updating Sprites", lambda succesful, resultmessage: done.set())
|
||||
try:
|
||||
top = Tk()
|
||||
except:
|
||||
task = BackgroundTaskProgressNullWindow(update_sprites, lambda successful, resultmessage: done.set())
|
||||
else:
|
||||
top.withdraw()
|
||||
task = BackgroundTaskProgress(top, update_sprites, "Updating Sprites", lambda succesful, resultmessage: done.set())
|
||||
while not done.isSet():
|
||||
top.update()
|
||||
task.do_events()
|
||||
|
||||
spriteData = []
|
||||
|
||||
|
||||
@@ -12,12 +12,9 @@ locations. So ,for example, Princess Sarah may have the CANOE instead of the LUT
|
||||
Pot or some armor. There are plenty of other things that can be randomized on our
|
||||
[main randomizer site](https://finalfantasyrandomizer.com/)
|
||||
|
||||
Some features are not currently supported by AP. A non-exhaustive list includes:
|
||||
- Shard Hunt
|
||||
- Deep Dungeon
|
||||
|
||||
## What Final Fantasy items can appear in other players' worlds?
|
||||
Currently, only progression items can appear in other players' worlds. Armor, Weapons and Consumable Items can not.
|
||||
All items can appear in other players worlds. This includes consumables, shards, weapons, armor and, of course,
|
||||
key items.
|
||||
|
||||
## What does another world's item look like in Final Fantasy
|
||||
All local and remote items appear the same. It will say that you received an item and then BOTH the client log and
|
||||
|
||||
@@ -10,7 +10,7 @@ so the game is always able to be completed. However, because of the item shuffle
|
||||
areas before they would in the vanilla game. For example, the Windwalker (flying machine) is accessible as soon as any
|
||||
weapon is obtained.
|
||||
|
||||
Additional help can be found in the [guide](https://github.com/black-sliver/evermizer/blob/feat-mw/guide.md).
|
||||
Additional help can be found in the [guide](https://github.com/black-sliver/evermizer/blob/master/guide.md).
|
||||
|
||||
## What items and locations get shuffled?
|
||||
All gourds/chests/pots, boss drops and alchemists are shuffled. Alchemy ingredients, sniff spot items, call bead spells
|
||||
|
||||
49
WebHostLib/static/assets/supermetroidTracker.js
Normal file
49
WebHostLib/static/assets/supermetroidTracker.js
Normal file
@@ -0,0 +1,49 @@
|
||||
window.addEventListener('load', () => {
|
||||
// Reload tracker every 15 seconds
|
||||
const url = window.location;
|
||||
setInterval(() => {
|
||||
const ajax = new XMLHttpRequest();
|
||||
ajax.onreadystatechange = () => {
|
||||
if (ajax.readyState !== 4) { return; }
|
||||
|
||||
// Create a fake DOM using the returned HTML
|
||||
const domParser = new DOMParser();
|
||||
const fakeDOM = domParser.parseFromString(ajax.responseText, 'text/html');
|
||||
|
||||
// Update item tracker
|
||||
document.getElementById('inventory-table').innerHTML = fakeDOM.getElementById('inventory-table').innerHTML;
|
||||
// Update only counters in the location-table
|
||||
let counters = document.getElementsByClassName('counter');
|
||||
const fakeCounters = fakeDOM.getElementsByClassName('counter');
|
||||
for (let i = 0; i < counters.length; i++) {
|
||||
counters[i].innerHTML = fakeCounters[i].innerHTML;
|
||||
}
|
||||
};
|
||||
ajax.open('GET', url);
|
||||
ajax.send();
|
||||
}, 15000)
|
||||
|
||||
// Collapsible advancement sections
|
||||
const categories = document.getElementsByClassName("location-category");
|
||||
for (let i = 0; i < categories.length; i++) {
|
||||
let hide_id = categories[i].id.split('-')[0];
|
||||
if (hide_id == 'Total') {
|
||||
continue;
|
||||
}
|
||||
categories[i].addEventListener('click', function() {
|
||||
// Toggle the advancement list
|
||||
document.getElementById(hide_id).classList.toggle("hide");
|
||||
// Change text of the header
|
||||
const tab_header = document.getElementById(hide_id+'-header').children[0];
|
||||
const orig_text = tab_header.innerHTML;
|
||||
let new_text;
|
||||
if (orig_text.includes("▼")) {
|
||||
new_text = orig_text.replace("▼", "▲");
|
||||
}
|
||||
else {
|
||||
new_text = orig_text.replace("▲", "▼");
|
||||
}
|
||||
tab_header.innerHTML = new_text;
|
||||
});
|
||||
}
|
||||
});
|
||||
@@ -76,7 +76,7 @@ that can be rolled by these settings. If a game can be rolled it **must** have a
|
||||
|
||||
Some options in Archipelago can be used by every game but must still be placed within the relevant game's section.
|
||||
Currently, these options are `start_inventory`, `start_hints`, `local_items`, `non_local_items`, `start_location_hints`,
|
||||
`exclude_locations`, and various [plando options](tutorial/archipelago/plando/en).
|
||||
`exclude_locations`, and various [plando options](/tutorial/archipelago/plando/en).
|
||||
* `start_inventory` will give any items defined here to you at the beginning of your game. The format for this must be
|
||||
the name as it appears in the game files and the amount you would like to start with. For example `Rupees(5): 6` which
|
||||
will give you 30 rupees.
|
||||
|
||||
104
WebHostLib/static/styles/supermetroidTracker.css
Normal file
104
WebHostLib/static/styles/supermetroidTracker.css
Normal file
@@ -0,0 +1,104 @@
|
||||
#player-tracker-wrapper{
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
#inventory-table{
|
||||
border-top: 2px solid #000000;
|
||||
border-left: 2px solid #000000;
|
||||
border-right: 2px solid #000000;
|
||||
border-top-left-radius: 4px;
|
||||
border-top-right-radius: 4px;
|
||||
padding: 3px 3px 10px;
|
||||
width: 384px;
|
||||
background-color: #546E7A;
|
||||
}
|
||||
|
||||
#inventory-table td{
|
||||
width: 45px;
|
||||
height: 45px;
|
||||
text-align: center;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
#inventory-table img{
|
||||
height: 100%;
|
||||
max-width: 40px;
|
||||
max-height: 40px;
|
||||
min-width: 40px;
|
||||
min-height: 40px;
|
||||
filter: grayscale(100%) contrast(75%) brightness(30%);
|
||||
}
|
||||
|
||||
#inventory-table img.acquired{
|
||||
filter: none;
|
||||
}
|
||||
|
||||
#inventory-table div.counted-item {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
#inventory-table div.item-count {
|
||||
position: absolute;
|
||||
color: white;
|
||||
font-family: "Minecraftia", monospace;
|
||||
font-weight: bold;
|
||||
bottom: 0px;
|
||||
right: 0px;
|
||||
}
|
||||
|
||||
#location-table{
|
||||
width: 384px;
|
||||
border-left: 2px solid #000000;
|
||||
border-right: 2px solid #000000;
|
||||
border-bottom: 2px solid #000000;
|
||||
border-bottom-left-radius: 4px;
|
||||
border-bottom-right-radius: 4px;
|
||||
background-color: #546E7A;
|
||||
color: #000000;
|
||||
padding: 0 3px 3px;
|
||||
font-size: 14px;
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
#location-table th{
|
||||
vertical-align: middle;
|
||||
text-align: left;
|
||||
padding-right: 10px;
|
||||
}
|
||||
|
||||
#location-table td{
|
||||
padding-top: 2px;
|
||||
padding-bottom: 2px;
|
||||
line-height: 20px;
|
||||
}
|
||||
|
||||
#location-table td.counter {
|
||||
text-align: right;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
#location-table td.toggle-arrow {
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
#location-table tr#Total-header {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
#location-table img{
|
||||
height: 100%;
|
||||
max-width: 30px;
|
||||
max-height: 30px;
|
||||
}
|
||||
|
||||
#location-table tbody.locations {
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
#location-table td.location-name {
|
||||
padding-left: 16px;
|
||||
}
|
||||
|
||||
.hide {
|
||||
display: none;
|
||||
}
|
||||
85
WebHostLib/templates/supermetroidTracker.html
Normal file
85
WebHostLib/templates/supermetroidTracker.html
Normal file
@@ -0,0 +1,85 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<title>{{ player_name }}'s Tracker</title>
|
||||
<link rel="stylesheet" type="text/css" href="{{ url_for('static', filename='styles/supermetroidTracker.css') }}"/>
|
||||
<script type="application/ecmascript" src="{{ url_for('static', filename='assets/supermetroidTracker.js') }}"></script>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div id="player-tracker-wrapper" data-tracker="{{ room.tracker|suuid }}">
|
||||
<table id="inventory-table">
|
||||
<tr>
|
||||
<td><img src="{{ icons['Charge Beam'] }}" class="{{ 'acquired' if 'Charge Beam' in acquired_items }}" title="Charge Beam" /></td>
|
||||
<td><img src="{{ icons['Ice Beam'] }}" class="{{ 'acquired' if 'Ice Beam' in acquired_items }}" title="Ice Beam" /></td>
|
||||
<td><img src="{{ icons['Wave Beam'] }}" class="{{ 'acquired' if 'Wave Beam' in acquired_items }}" title="Wave Beam" /></td>
|
||||
<td><img src="{{ icons['Spazer'] }}" class="{{ 'acquired' if 'Spazer' in acquired_items }}" title="S p a z e r" /></td>
|
||||
<td><img src="{{ icons['Plasma Beam'] }}" class="{{ 'acquired' if 'Plasma Beam' in acquired_items }}" title="Plasma Beam" /></td>
|
||||
<td><img src="{{ icons['Varia Suit'] }}" class="{{ 'acquired' if 'Varia Suit' in acquired_items }}" title="Varia Suit" /></td>
|
||||
<td><img src="{{ icons['Gravity Suit'] }}" class="{{ 'acquired' if 'Gravity Suit' in acquired_items }}" title="Gravity Suit" /></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><img src="{{ icons['Morph Ball'] }}" class="{{ 'acquired' if 'Morph Ball' in acquired_items }}" title="Morph Ball" /></td>
|
||||
<td><img src="{{ icons['Bomb'] }}" class="{{ 'acquired' if 'Bomb' in acquired_items }}" title="Bomb" /></td>
|
||||
<td><img src="{{ icons['Spring Ball'] }}" class="{{ 'acquired' if 'Spring Ball' in acquired_items }}" title="Spring Ball" /></td>
|
||||
<td><img src="{{ icons['Screw Attack'] }}" class="{{ 'acquired' if 'Screw Attack' in acquired_items }}" title="Screw Attack" /></td>
|
||||
<td><img src="{{ icons['Hi-Jump Boots'] }}" class="{{ 'acquired' if 'Hi-Jump Boots' in acquired_items }}" title="Hi-Jump Boots" /></td>
|
||||
<td><img src="{{ icons['Space Jump'] }}" class="{{ 'acquired' if 'Space Jump' in acquired_items }}" title="Space Jump" /></td>
|
||||
<td><img src="{{ icons['Speed Booster'] }}" class="{{ 'acquired' if 'Speed Booster' in acquired_items }}" title="Speed Booster" /></td>
|
||||
|
||||
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<div class="counted-item">
|
||||
<img src="{{ icons['Energy Tank'] }}" class="{{ 'acquired' if energy_count > 0 }}" title="Energy Tank" />
|
||||
<div class="item-count">{{ energy_count }}</div>
|
||||
</div>
|
||||
</td>
|
||||
<td>
|
||||
<div class="counted-item">
|
||||
<img src="{{ icons['Reserve Tank'] }}" class="{{ 'acquired' if reserve_count > 0 }}" title="Reserve Tank" />
|
||||
<div class="item-count">{{ reserve_count }}</div>
|
||||
</div>
|
||||
</td>
|
||||
<td>
|
||||
<div class="counted-item">
|
||||
<img src="{{ icons['Missile'] }}" class="{{ 'acquired' if missile_count > 0 }}" title="Missile ({{missile_count * 5}})" />
|
||||
<div class="item-count">{{ missile_count }}</div>
|
||||
</div>
|
||||
</td>
|
||||
<td>
|
||||
<div class="counted-item">
|
||||
<img src="{{ icons['Super Missile'] }}" class="{{ 'acquired' if super_count > 0 }}" title="Super Missile ({{super_count * 5}})" />
|
||||
<div class="item-count">{{ super_count }}</div>
|
||||
</div>
|
||||
</td>
|
||||
<td>
|
||||
<div class="counted-item">
|
||||
<img src="{{ icons['Power Bomb'] }}" class="{{ 'acquired' if power_count > 0 }}" title="Power Bomb ({{power_count * 5}})" />
|
||||
<div class="item-count">{{ power_count }}</div>
|
||||
</div>
|
||||
</td>
|
||||
<td><img src="{{ icons['Grappling Beam'] }}" class="{{ 'acquired' if 'Grappling Beam' in acquired_items }}" title="Grappling Beam" /></td>
|
||||
<td><img src="{{ icons['X-Ray Scope'] }}" class="{{ 'acquired' if 'X-Ray Scope' in acquired_items }}" title="X-Ray Scope" /></td>
|
||||
</tr>
|
||||
</table>
|
||||
<table id="location-table">
|
||||
{% for area in checks_done %}
|
||||
<tr class="location-category" id="{{area}}-header">
|
||||
<td>{{ area }} {{'▼' if area != 'Total'}}</td>
|
||||
<td class="counter">{{ checks_done[area] }} / {{ checks_in_area[area] }}</td>
|
||||
</tr>
|
||||
<tbody class="locations hide" id="{{area}}">
|
||||
{% for location in location_info[area] %}
|
||||
<tr>
|
||||
<td class="location-name">{{ location }}</td>
|
||||
<td class="counter">{{ '✔' if location_info[area][location] else '' }}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
{% endfor %}
|
||||
</table>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -774,6 +774,106 @@ def __renderTimespinnerTracker(multisave: Dict[str, Any], room: Room, locations:
|
||||
checks_done=checks_done, checks_in_area=checks_in_area, location_info=location_info,
|
||||
**display_data)
|
||||
|
||||
def __renderSuperMetroidTracker(multisave: Dict[str, Any], room: Room, locations: Dict[int, Dict[int, Tuple[int, int]]],
|
||||
inventory: Counter, team: int, player: int, playerName: str,
|
||||
seed_checks_in_area: Dict[int, Dict[str, int]], checks_done: Dict[str, int]) -> str:
|
||||
|
||||
icons = {
|
||||
"Energy Tank": "https://randommetroidsolver.pythonanywhere.com/solver/static/images/ETank.png",
|
||||
"Missile": "https://randommetroidsolver.pythonanywhere.com/solver/static/images/Missile.png",
|
||||
"Super Missile": "https://randommetroidsolver.pythonanywhere.com/solver/static/images/Super.png",
|
||||
"Power Bomb": "https://randommetroidsolver.pythonanywhere.com/solver/static/images/PowerBomb.png",
|
||||
"Bomb": "https://randommetroidsolver.pythonanywhere.com/solver/static/images/Bomb.png",
|
||||
"Charge Beam": "https://randommetroidsolver.pythonanywhere.com/solver/static/images/Charge.png",
|
||||
"Ice Beam": "https://randommetroidsolver.pythonanywhere.com/solver/static/images/Ice.png",
|
||||
"Hi-Jump Boots": "https://randommetroidsolver.pythonanywhere.com/solver/static/images/HiJump.png",
|
||||
"Speed Booster": "https://randommetroidsolver.pythonanywhere.com/solver/static/images/SpeedBooster.png",
|
||||
"Wave Beam": "https://randommetroidsolver.pythonanywhere.com/solver/static/images/Wave.png",
|
||||
"Spazer": "https://randommetroidsolver.pythonanywhere.com/solver/static/images/Spazer.png",
|
||||
"Spring Ball": "https://randommetroidsolver.pythonanywhere.com/solver/static/images/SpringBall.png",
|
||||
"Varia Suit": "https://randommetroidsolver.pythonanywhere.com/solver/static/images/Varia.png",
|
||||
"Plasma Beam": "https://randommetroidsolver.pythonanywhere.com/solver/static/images/Plasma.png",
|
||||
"Grappling Beam": "https://randommetroidsolver.pythonanywhere.com/solver/static/images/Grapple.png",
|
||||
"Morph Ball": "https://randommetroidsolver.pythonanywhere.com/solver/static/images/Morph.png",
|
||||
"Reserve Tank": "https://randommetroidsolver.pythonanywhere.com/solver/static/images/Reserve.png",
|
||||
"Gravity Suit": "https://randommetroidsolver.pythonanywhere.com/solver/static/images/Gravity.png",
|
||||
"X-Ray Scope": "https://randommetroidsolver.pythonanywhere.com/solver/static/images/XRayScope.png",
|
||||
"Space Jump": "https://randommetroidsolver.pythonanywhere.com/solver/static/images/SpaceJump.png",
|
||||
"Screw Attack": "https://randommetroidsolver.pythonanywhere.com/solver/static/images/ScrewAttack.png",
|
||||
"Nothing": "",
|
||||
"No Energy": "",
|
||||
"Kraid": "",
|
||||
"Phantoon": "",
|
||||
"Draygon": "",
|
||||
"Ridley": "",
|
||||
"Mother Brain": "",
|
||||
}
|
||||
|
||||
multi_items = {
|
||||
"Energy Tank": 83000,
|
||||
"Missile": 83001,
|
||||
"Super Missile": 83002,
|
||||
"Power Bomb": 83003,
|
||||
"Reserve Tank": 83020,
|
||||
}
|
||||
|
||||
supermetroid_location_ids = {
|
||||
'Crateria/Blue Brinstar': [82005, 82007, 82008, 82026, 82029,
|
||||
82000, 82004, 82006, 82009, 82010,
|
||||
82011, 82012, 82027, 82028, 82034,
|
||||
82036, 82037],
|
||||
'Green/Pink Brinstar': [82017, 82023, 82030, 82033, 82035,
|
||||
82013, 82014, 82015, 82016, 82018,
|
||||
82019, 82021, 82022, 82024, 82025,
|
||||
82031],
|
||||
'Red Brinstar': [82038, 82042, 82039, 82040, 82041],
|
||||
'Kraid': [82043, 82048, 82044],
|
||||
'Norfair': [82050, 82053, 82061, 82066, 82068,
|
||||
82049, 82051, 82054, 82055, 82056,
|
||||
82062, 82063, 82064, 82065, 82067],
|
||||
'Lower Norfair': [82078, 82079, 82080, 82070, 82071,
|
||||
82073, 82074, 82075, 82076, 82077],
|
||||
'Crocomire': [82052, 82060, 82057, 82058, 82059],
|
||||
'Wrecked Ship': [82129, 82132, 82134, 82135, 82001,
|
||||
82002, 82003, 82128, 82130, 82131,
|
||||
82133],
|
||||
'West Maridia': [82138, 82136, 82137, 82139, 82140,
|
||||
82141, 82142],
|
||||
'East Maridia': [82143, 82145, 82150, 82152, 82154,
|
||||
82144, 82146, 82147, 82148, 82149,
|
||||
82151],
|
||||
}
|
||||
|
||||
display_data = {}
|
||||
|
||||
|
||||
for item_name, item_id in multi_items.items():
|
||||
base_name = item_name.split()[0].lower()
|
||||
count = inventory[item_id]
|
||||
display_data[base_name+"_count"] = inventory[item_id]
|
||||
|
||||
# Victory condition
|
||||
game_state = multisave.get("client_game_state", {}).get((team, player), 0)
|
||||
display_data['game_finished'] = game_state == 30
|
||||
|
||||
# Turn location IDs into advancement tab counts
|
||||
checked_locations = multisave.get("location_checks", {}).get((team, player), set())
|
||||
lookup_name = lambda id: lookup_any_location_id_to_name[id]
|
||||
location_info = {tab_name: {lookup_name(id): (id in checked_locations) for id in tab_locations}
|
||||
for tab_name, tab_locations in supermetroid_location_ids.items()}
|
||||
checks_done = {tab_name: len([id for id in tab_locations if id in checked_locations])
|
||||
for tab_name, tab_locations in supermetroid_location_ids.items()}
|
||||
checks_done['Total'] = len(checked_locations)
|
||||
checks_in_area = {tab_name: len(tab_locations) for tab_name, tab_locations in supermetroid_location_ids.items()}
|
||||
checks_in_area['Total'] = sum(checks_in_area.values())
|
||||
|
||||
return render_template("supermetroidTracker.html",
|
||||
inventory=inventory, icons=icons,
|
||||
acquired_items={lookup_any_item_id_to_name[id] for id in inventory if
|
||||
id in lookup_any_item_id_to_name},
|
||||
player=player, team=team, room=room, player_name=playerName,
|
||||
checks_done=checks_done, checks_in_area=checks_in_area, location_info=location_info,
|
||||
**display_data)
|
||||
|
||||
def __renderGenericTracker(multisave: Dict[str, Any], room: Room, locations: Dict[int, Dict[int, Tuple[int, int]]],
|
||||
inventory: Counter, team: int, player: int, playerName: str,
|
||||
@@ -887,5 +987,6 @@ game_specific_trackers: typing.Dict[str, typing.Callable] = {
|
||||
"Minecraft": __renderMinecraftTracker,
|
||||
"Ocarina of Time": __renderOoTTracker,
|
||||
"Timespinner": __renderTimespinnerTracker,
|
||||
"A Link to the Past": __renderAlttpTracker
|
||||
"A Link to the Past": __renderAlttpTracker,
|
||||
"Super Metroid": __renderSuperMetroidTracker
|
||||
}
|
||||
9
kvui.py
9
kvui.py
@@ -8,11 +8,16 @@ os.environ["KIVY_NO_FILELOG"] = "1"
|
||||
os.environ["KIVY_NO_ARGS"] = "1"
|
||||
os.environ["KIVY_LOG_ENABLE"] = "0"
|
||||
|
||||
from kivy.base import Config
|
||||
Config.set("input", "mouse", "mouse,disable_multitouch")
|
||||
Config.set('kivy', 'exit_on_escape', '0')
|
||||
Config.set('graphics', 'multisamples', '0') # multisamples crash old intel drivers
|
||||
|
||||
from kivy.app import App
|
||||
from kivy.core.window import Window
|
||||
from kivy.core.clipboard import Clipboard
|
||||
from kivy.core.text.markup import MarkupLabel
|
||||
from kivy.base import ExceptionHandler, ExceptionManager, Config, Clock
|
||||
from kivy.base import ExceptionHandler, ExceptionManager, Clock
|
||||
from kivy.factory import Factory
|
||||
from kivy.properties import BooleanProperty, ObjectProperty
|
||||
from kivy.uix.button import Button
|
||||
@@ -431,6 +436,4 @@ class KivyJSONtoTextParser(JSONtoTextParser):
|
||||
|
||||
ExceptionManager.add_handler(E())
|
||||
|
||||
Config.set("input", "mouse", "mouse,disable_multitouch")
|
||||
Config.set('kivy', 'exit_on_escape', '0')
|
||||
Builder.load_file(Utils.local_path("data", "client.kv"))
|
||||
|
||||
@@ -2,6 +2,6 @@ colorama>=0.4.4
|
||||
websockets>=10.1
|
||||
PyYAML>=6.0
|
||||
fuzzywuzzy>=0.18.0
|
||||
appdirs>=1.4.4
|
||||
jinja2>=3.0.3
|
||||
schema>=0.7.4
|
||||
kivy>=2.0.0
|
||||
|
||||
3
setup.py
3
setup.py
@@ -141,7 +141,7 @@ for folder in sdl2.dep_bins + glew.dep_bins:
|
||||
shutil.copytree(folder, libfolder, dirs_exist_ok=True)
|
||||
print('copying', folder, '->', libfolder)
|
||||
|
||||
extra_data = ["LICENSE", "data", "EnemizerCLI", "host.yaml", "SNI", "meta.yaml"]
|
||||
extra_data = ["LICENSE", "data", "EnemizerCLI", "host.yaml", "SNI"]
|
||||
|
||||
for data in extra_data:
|
||||
installfile(Path(data))
|
||||
@@ -155,6 +155,7 @@ for worldname, worldtype in AutoWorldRegister.world_types.items():
|
||||
file_name = worldname+".yaml"
|
||||
shutil.copyfile(os.path.join("WebHostLib", "static", "generated", "configs", file_name),
|
||||
buildfolder / "Players" / "Templates" / file_name)
|
||||
shutil.copyfile("meta.yaml", buildfolder / "Players" / "Templates" / "meta.yaml")
|
||||
|
||||
try:
|
||||
from maseya import z3pr
|
||||
|
||||
@@ -2287,7 +2287,7 @@ def write_strings(rom, world, player):
|
||||
if hint_count:
|
||||
locations = world.find_items_in_locations(set(items_to_hint), player)
|
||||
local_random.shuffle(locations)
|
||||
for x in range(hint_count):
|
||||
for x in range(min(hint_count, len(locations))):
|
||||
this_location = locations.pop()
|
||||
this_hint = this_location.item.hint_text + ' can be found ' + hint_text(this_location) + '.'
|
||||
tt[hint_locations.pop(0)] = this_hint
|
||||
|
||||
@@ -94,6 +94,8 @@ class TechTreeLayout(Choice):
|
||||
option_small_funnels = 7
|
||||
option_medium_funnels = 8
|
||||
option_large_funnels = 9
|
||||
option_trees = 10
|
||||
option_choices = 11
|
||||
default = 0
|
||||
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
from typing import Dict, List, Set
|
||||
from collections import deque
|
||||
|
||||
from worlds.factorio.Options import TechTreeLayout
|
||||
|
||||
@@ -21,7 +22,9 @@ def get_shapes(factorio_world) -> Dict[str, List[str]]:
|
||||
tech_names.sort()
|
||||
world.random.shuffle(tech_names)
|
||||
|
||||
if layout == TechTreeLayout.option_small_diamonds:
|
||||
if layout == TechTreeLayout.option_single:
|
||||
pass
|
||||
elif layout == TechTreeLayout.option_small_diamonds:
|
||||
slice_size = 4
|
||||
while len(tech_names) > slice_size:
|
||||
slice = tech_names[:slice_size]
|
||||
@@ -189,6 +192,55 @@ def get_shapes(factorio_world) -> Dict[str, List[str]]:
|
||||
prerequisites.setdefault(tech_name, set()).update(previous_slice[i:i+2])
|
||||
previous_slice = slice
|
||||
layer_size -= 1
|
||||
elif layout == TechTreeLayout.option_trees:
|
||||
# 0 |
|
||||
# 1 2 |
|
||||
# 3 |
|
||||
# 4 5 6 7 |
|
||||
# 8 |
|
||||
# 9 10 11 12 13 14 |
|
||||
# 15 |
|
||||
# 16 |
|
||||
slice_size = 17
|
||||
while len(tech_names) > slice_size:
|
||||
slice = tech_names[:slice_size]
|
||||
tech_names = tech_names[slice_size:]
|
||||
slice.sort(key=lambda tech_name: len(custom_technologies[tech_name].get_prior_technologies()))
|
||||
|
||||
prerequisites[slice[1]] = {slice[0]}
|
||||
prerequisites[slice[2]] = {slice[0]}
|
||||
|
||||
prerequisites[slice[3]] = {slice[1], slice[2]}
|
||||
|
||||
prerequisites[slice[4]] = {slice[3]}
|
||||
prerequisites[slice[5]] = {slice[3]}
|
||||
prerequisites[slice[6]] = {slice[3]}
|
||||
prerequisites[slice[7]] = {slice[3]}
|
||||
|
||||
prerequisites[slice[8]] = {slice[4], slice[5], slice[6], slice[7]}
|
||||
|
||||
prerequisites[slice[9]] = {slice[8]}
|
||||
prerequisites[slice[10]] = {slice[8]}
|
||||
prerequisites[slice[11]] = {slice[8]}
|
||||
prerequisites[slice[12]] = {slice[8]}
|
||||
prerequisites[slice[13]] = {slice[8]}
|
||||
prerequisites[slice[14]] = {slice[8]}
|
||||
|
||||
prerequisites[slice[15]] = {slice[9], slice[10], slice[11], slice[12], slice[13], slice[14]}
|
||||
prerequisites[slice[16]] = {slice[15]}
|
||||
elif layout == TechTreeLayout.option_choices:
|
||||
tech_names.sort(key=lambda tech_name: len(custom_technologies[tech_name].get_prior_technologies()))
|
||||
current_choices = deque([tech_names[0]])
|
||||
tech_names = tech_names[1:]
|
||||
while len(tech_names) > 1:
|
||||
source = current_choices.pop()
|
||||
choices = tech_names[:2]
|
||||
tech_names = tech_names[2:]
|
||||
for choice in choices:
|
||||
prerequisites[choice] = {source}
|
||||
current_choices.extendleft(choices)
|
||||
else:
|
||||
raise NotImplementedError(f"Layout {layout} is not implemented.")
|
||||
|
||||
world.tech_tree_layout_prerequisites[player] = prerequisites
|
||||
return prerequisites
|
||||
|
||||
@@ -1,3 +1,2 @@
|
||||
kivy>=2.0.0
|
||||
factorio-rcon-py>=1.2.1
|
||||
schema>=0.7.4
|
||||
|
||||
@@ -282,22 +282,22 @@ class Rom(BigStream):
|
||||
|
||||
|
||||
def compress_rom_file(input_file, output_file):
|
||||
subcall = []
|
||||
|
||||
compressor_path = data_path("Compress")
|
||||
|
||||
if platform.system() == 'Windows':
|
||||
compressor_path += "\\Compress.exe"
|
||||
executable_path = "Compress.exe"
|
||||
elif platform.system() == 'Linux':
|
||||
if platform.uname()[4] == 'aarch64' or platform.uname()[4] == 'arm64':
|
||||
compressor_path += "/Compress_ARM64"
|
||||
executable_path = "Compress_ARM64"
|
||||
else:
|
||||
compressor_path += "/Compress"
|
||||
executable_path = "Compress"
|
||||
elif platform.system() == 'Darwin':
|
||||
compressor_path += "/Compress.out"
|
||||
executable_path = "Compress.out"
|
||||
else:
|
||||
raise RuntimeError('Unsupported operating system for compression.')
|
||||
|
||||
compressor_path = os.path.join(compressor_path, executable_path)
|
||||
if not os.path.exists(compressor_path):
|
||||
raise RuntimeError(f'Compressor does not exist! Please place it at {compressor_path}.')
|
||||
process = subprocess.call([compressor_path, input_file, output_file], **subprocess_args(include_stdout=False))
|
||||
import logging
|
||||
logging.info(subprocess.check_output([compressor_path, input_file, output_file],
|
||||
**subprocess_args(include_stdout=False)))
|
||||
|
||||
@@ -42,7 +42,7 @@ class StartLocation(Choice):
|
||||
|
||||
class DeathLink(Choice):
|
||||
"""When DeathLink is enabled and someone dies, you will die. With survive reserve tanks can save you."""
|
||||
displayname = "Death Link Survive"
|
||||
displayname = "Death Link"
|
||||
option_disable = 0
|
||||
option_enable = 1
|
||||
option_enable_survive = 3
|
||||
|
||||
@@ -348,8 +348,7 @@ class VariaRandomizer:
|
||||
if response.ok:
|
||||
PresetLoader.factory(json.loads(response.text)).load(self.player)
|
||||
else:
|
||||
print("Got error {} {} {} from trying to fetch varia custom preset named {}".format(response.status_code, response.reason, response.text, preset_name))
|
||||
sys.exit(-1)
|
||||
raise Exception("Got error {} {} {} from trying to fetch varia custom preset named {}".format(response.status_code, response.reason, response.text, preset_name))
|
||||
else:
|
||||
preset = 'default'
|
||||
PresetLoader.factory(os.path.join(appDir, getPresetDir('casual'), 'casual.json')).load(self.player)
|
||||
@@ -365,13 +364,11 @@ class VariaRandomizer:
|
||||
self.seed = args.seed
|
||||
logger.debug("seed: {}".format(self.seed))
|
||||
|
||||
seed4rand = self.seed
|
||||
if args.raceMagic is not None:
|
||||
if args.raceMagic <= 0 or args.raceMagic >= 0x10000:
|
||||
print("Invalid magic")
|
||||
sys.exit(-1)
|
||||
seed4rand = self.seed ^ args.raceMagic
|
||||
# random.seed(seed4rand)
|
||||
|
||||
# if no max diff, set it very high
|
||||
if args.maxDifficulty:
|
||||
if args.maxDifficulty == 'random':
|
||||
|
||||
@@ -16,10 +16,11 @@ class EvermizerFlag:
|
||||
return self.flag if self.value != self.default else ''
|
||||
|
||||
|
||||
class OffOnChaosChoice(Choice):
|
||||
class OffOnFullChoice(Choice):
|
||||
option_off = 0
|
||||
option_on = 1
|
||||
option_chaos = 2
|
||||
option_full = 2
|
||||
alias_chaos = 2
|
||||
alias_false = 0
|
||||
alias_true = 1
|
||||
|
||||
@@ -30,7 +31,8 @@ class Difficulty(EvermizerFlags, Choice):
|
||||
option_easy = 0
|
||||
option_normal = 1
|
||||
option_hard = 2
|
||||
option_chaos = 3 # random is reserved pre 0.2
|
||||
option_mystery = 3 # 'random' is reserved
|
||||
alias_chaos = 3
|
||||
default = 1
|
||||
flags = ['e', 'n', 'h', 'x']
|
||||
|
||||
@@ -88,27 +90,27 @@ class ShorterDialogs(EvermizerFlag, Toggle):
|
||||
|
||||
|
||||
class ShortBossRush(EvermizerFlag, Toggle):
|
||||
"""Start boss rush at Magmar, cut HP in half"""
|
||||
"""Start boss rush at Metal Magmar, cut enemy HP in half"""
|
||||
displayname = "Short Boss Rush"
|
||||
flag = 'f'
|
||||
|
||||
|
||||
class Ingredienizer(EvermizerFlags, OffOnChaosChoice):
|
||||
"""Shuffles or randomizes spell ingredients"""
|
||||
class Ingredienizer(EvermizerFlags, OffOnFullChoice):
|
||||
"""On Shuffles, Full randomizes spell ingredients"""
|
||||
displayname = "Ingredienizer"
|
||||
default = 1
|
||||
flags = ['i', '', 'I']
|
||||
|
||||
|
||||
class Sniffamizer(EvermizerFlags, OffOnChaosChoice):
|
||||
"""Shuffles or randomizes drops in sniff locations"""
|
||||
class Sniffamizer(EvermizerFlags, OffOnFullChoice):
|
||||
"""On Shuffles, Full randomizes drops in sniff locations"""
|
||||
displayname = "Sniffamizer"
|
||||
default = 1
|
||||
flags = ['s', '', 'S']
|
||||
|
||||
|
||||
class Callbeadamizer(EvermizerFlags, OffOnChaosChoice):
|
||||
"""Shuffles call bead characters or spells"""
|
||||
class Callbeadamizer(EvermizerFlags, OffOnFullChoice):
|
||||
"""On Shuffles call bead characters, Full shuffles individual spells"""
|
||||
displayname = "Callbeadamizer"
|
||||
default = 1
|
||||
flags = ['c', '', 'C']
|
||||
@@ -120,8 +122,8 @@ class Musicmizer(EvermizerFlag, Toggle):
|
||||
flag = 'm'
|
||||
|
||||
|
||||
class Doggomizer(EvermizerFlags, OffOnChaosChoice):
|
||||
"""On shuffles dog per act, Chaos randomizes dog per screen, Pupdunk gives you Everpupper everywhere"""
|
||||
class Doggomizer(EvermizerFlags, OffOnFullChoice):
|
||||
"""On shuffles dog per act, Full randomizes dog per screen, Pupdunk gives you Everpupper everywhere"""
|
||||
displayname = "Doggomizer"
|
||||
option_pupdunk = 3
|
||||
default = 0
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
https://github.com/black-sliver/pyevermizer/releases/download/v0.39.2/pyevermizer-0.39.2-cp38-cp38-win_amd64.whl#egg=pyevermizer; sys_platform == 'win32' and platform_machine == 'AMD64' and python_version == '3.8'
|
||||
https://github.com/black-sliver/pyevermizer/releases/download/v0.39.2/pyevermizer-0.39.2-cp39-cp39-win_amd64.whl#egg=pyevermizer; sys_platform == 'win32' and platform_machine == 'AMD64' and python_version == '3.9'
|
||||
https://github.com/black-sliver/pyevermizer/releases/download/v0.39.2/pyevermizer-0.39.2-cp310-cp310-win_amd64.whl#egg=pyevermizer; sys_platform == 'win32' and platform_machine == 'AMD64' and python_version == '3.10'
|
||||
https://github.com/black-sliver/pyevermizer/releases/download/v0.39.2/pyevermizer-0.39.2-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl#egg=pyevermizer; sys_platform == 'linux' and platform_machine == 'x86_64' and python_version == '3.8'
|
||||
https://github.com/black-sliver/pyevermizer/releases/download/v0.39.2/pyevermizer-0.39.2-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl#egg=pyevermizer; sys_platform == 'linux' and platform_machine == 'x86_64' and python_version == '3.9'
|
||||
https://github.com/black-sliver/pyevermizer/releases/download/v0.39.2/pyevermizer-0.39.2-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl#egg=pyevermizer; sys_platform == 'linux' and platform_machine == 'x86_64' and python_version == '3.10'
|
||||
https://github.com/black-sliver/pyevermizer/releases/download/v0.39.2/pyevermizer-0.39.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl#egg=pyevermizer; sys_platform == 'linux' and platform_machine == 'aarch64' and python_version == '3.8'
|
||||
https://github.com/black-sliver/pyevermizer/releases/download/v0.39.2/pyevermizer-0.39.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl#egg=pyevermizer; sys_platform == 'linux' and platform_machine == 'aarch64' and python_version == '3.9'
|
||||
https://github.com/black-sliver/pyevermizer/releases/download/v0.39.2/pyevermizer-0.39.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl#egg=pyevermizer; sys_platform == 'linux' and platform_machine == 'aarch64' and python_version == '3.10'
|
||||
https://github.com/black-sliver/pyevermizer/releases/download/v0.39.2/pyevermizer-0.39.2-cp38-cp38-macosx_10_9_amd64.whl#egg=pyevermizer; sys_platform == 'darwin' and python_version == '3.8'
|
||||
#https://github.com/black-sliver/pyevermizer/releases/download/v0.39.2/pyevermizer-0.39.2-cp39-cp39-macosx_10_9_amd64.whl#egg=pyevermizer; sys_platform == 'darwin' and python_version == '3.9'
|
||||
https://github.com/black-sliver/pyevermizer/releases/download/v0.39.2/pyevermizer-0.39.2-cp39-cp39-macosx_10_9_universal2.whl#egg=pyevermizer; sys_platform == 'darwin' and python_version == '3.9'
|
||||
https://github.com/black-sliver/pyevermizer/releases/download/v0.39.2/pyevermizer-0.39.2-cp310-cp310-macosx_10_9_universal2.whl#egg=pyevermizer; sys_platform == 'darwin' and python_version == '3.10'
|
||||
https://github.com/black-sliver/pyevermizer/releases/download/v0.40.0/pyevermizer-0.40.0-cp38-cp38-win_amd64.whl#egg=pyevermizer; sys_platform == 'win32' and platform_machine == 'AMD64' and python_version == '3.8'
|
||||
https://github.com/black-sliver/pyevermizer/releases/download/v0.40.0/pyevermizer-0.40.0-cp39-cp39-win_amd64.whl#egg=pyevermizer; sys_platform == 'win32' and platform_machine == 'AMD64' and python_version == '3.9'
|
||||
https://github.com/black-sliver/pyevermizer/releases/download/v0.40.0/pyevermizer-0.40.0-cp310-cp310-win_amd64.whl#egg=pyevermizer; sys_platform == 'win32' and platform_machine == 'AMD64' and python_version == '3.10'
|
||||
https://github.com/black-sliver/pyevermizer/releases/download/v0.40.0/pyevermizer-0.40.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl#egg=pyevermizer; sys_platform == 'linux' and platform_machine == 'x86_64' and python_version == '3.8'
|
||||
https://github.com/black-sliver/pyevermizer/releases/download/v0.40.0/pyevermizer-0.40.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl#egg=pyevermizer; sys_platform == 'linux' and platform_machine == 'x86_64' and python_version == '3.9'
|
||||
https://github.com/black-sliver/pyevermizer/releases/download/v0.40.0/pyevermizer-0.40.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl#egg=pyevermizer; sys_platform == 'linux' and platform_machine == 'x86_64' and python_version == '3.10'
|
||||
https://github.com/black-sliver/pyevermizer/releases/download/v0.40.0/pyevermizer-0.40.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl#egg=pyevermizer; sys_platform == 'linux' and platform_machine == 'aarch64' and python_version == '3.8'
|
||||
https://github.com/black-sliver/pyevermizer/releases/download/v0.40.0/pyevermizer-0.40.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl#egg=pyevermizer; sys_platform == 'linux' and platform_machine == 'aarch64' and python_version == '3.9'
|
||||
https://github.com/black-sliver/pyevermizer/releases/download/v0.40.0/pyevermizer-0.40.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl#egg=pyevermizer; sys_platform == 'linux' and platform_machine == 'aarch64' and python_version == '3.10'
|
||||
https://github.com/black-sliver/pyevermizer/releases/download/v0.40.0/pyevermizer-0.40.0-cp38-cp38-macosx_10_9_x86_64.whl#egg=pyevermizer; sys_platform == 'darwin' and python_version == '3.8'
|
||||
#https://github.com/black-sliver/pyevermizer/releases/download/v0.40.0/pyevermizer-0.40.0-cp39-cp39-macosx_10_9_x86_64.whl#egg=pyevermizer; sys_platform == 'darwin' and python_version == '3.9'
|
||||
https://github.com/black-sliver/pyevermizer/releases/download/v0.40.0/pyevermizer-0.40.0-cp39-cp39-macosx_10_9_universal2.whl#egg=pyevermizer; sys_platform == 'darwin' and python_version == '3.9'
|
||||
https://github.com/black-sliver/pyevermizer/releases/download/v0.40.0/pyevermizer-0.40.0-cp310-cp310-macosx_10_9_universal2.whl#egg=pyevermizer; sys_platform == 'darwin' and python_version == '3.10'
|
||||
bsdiff4>=1.2.1
|
||||
|
||||
@@ -213,6 +213,7 @@ starter_melee_weapons: Tuple[str, ...] = (
|
||||
)
|
||||
|
||||
starter_spells: Tuple[str, ...] = (
|
||||
'Aura Blast',
|
||||
'Colossal Blade',
|
||||
'Infernal Flames',
|
||||
'Plasma Geyser',
|
||||
|
||||
@@ -50,6 +50,10 @@ class Cantoran(Toggle):
|
||||
"Cantoran's fight and check are available upon revisiting his room"
|
||||
display_name = "Cantoran"
|
||||
|
||||
class DamageRando(Toggle):
|
||||
"Each orb has a high chance of having lower base damage and a low chance of having much higher base damage."
|
||||
display_name = "Damage Rando"
|
||||
|
||||
# Some options that are available in the timespinner randomizer arent currently implemented
|
||||
timespinner_options: Dict[str, Toggle] = {
|
||||
"StartWithJewelryBox": StartWithJewelryBox,
|
||||
@@ -64,6 +68,7 @@ timespinner_options: Dict[str, Toggle] = {
|
||||
#"StinkyMaw": StinkyMaw,
|
||||
"GyreArchives": GyreArchives,
|
||||
"Cantoran": Cantoran,
|
||||
"DamageRando": DamageRando,
|
||||
"DeathLink": DeathLink,
|
||||
}
|
||||
|
||||
|
||||
@@ -27,4 +27,7 @@ def get_pyramid_keys_unlock(world: MultiWorld, player: int) -> str:
|
||||
else:
|
||||
gates = (*past_teleportation_gates, *present_teleportation_gates)
|
||||
|
||||
if not world:
|
||||
return gates[0]
|
||||
|
||||
return world.random.choice(gates)
|
||||
@@ -1,4 +1,4 @@
|
||||
from typing import List, Dict, Tuple, Optional, Callable
|
||||
from typing import List, Set, Dict, Tuple, Optional, Callable
|
||||
from BaseClasses import MultiWorld, Region, Entrance, Location, RegionType
|
||||
from .Options import is_option_enabled
|
||||
from .Locations import LocationData
|
||||
@@ -6,7 +6,7 @@ from .Locations import LocationData
|
||||
def create_regions(world: MultiWorld, player: int, locations: Tuple[LocationData, ...], location_cache: List[Location], pyramid_keys_unlock: str):
|
||||
locations_per_region = get_locations_per_region(locations)
|
||||
|
||||
world.regions += [
|
||||
regions = [
|
||||
create_region(world, player, locations_per_region, location_cache, 'Menu'),
|
||||
create_region(world, player, locations_per_region, location_cache, 'Tutorial'),
|
||||
create_region(world, player, locations_per_region, location_cache, 'Lake desolation'),
|
||||
@@ -45,6 +45,11 @@ def create_regions(world: MultiWorld, player: int, locations: Tuple[LocationData
|
||||
create_region(world, player, locations_per_region, location_cache, 'Space time continuum')
|
||||
]
|
||||
|
||||
if __debug__:
|
||||
throwIfAnyLocationIsNotAssignedToARegion(regions, locations_per_region.keys())
|
||||
|
||||
world.regions += regions
|
||||
|
||||
connectStartingRegion(world, player)
|
||||
|
||||
names: Dict[str, int] = {}
|
||||
@@ -94,8 +99,8 @@ def create_regions(world: MultiWorld, player: int, locations: Tuple[LocationData
|
||||
connect(world, player, names, 'Skeleton Shaft', 'Sealed Caves (upper)', lambda state: state._timespinner_has_keycard_A(world, player))
|
||||
connect(world, player, names, 'Skeleton Shaft', 'Space time continuum', lambda state: state.has('Twin Pyramid Key', player))
|
||||
connect(world, player, names, 'Sealed Caves (upper)', 'Skeleton Shaft')
|
||||
connect(world, player, names, 'Sealed Caves (upper)', 'Sealed Caves (Xarion)', lambda state: state.has('Twin Pyramid Key', player) or state._timespinner_has_forwarddash_doublejump(world, player))
|
||||
connect(world, player, names, 'Sealed Caves (Xarion)', 'Sealed Caves (upper)', lambda state: state._timespinner_has_forwarddash_doublejump(world, player))
|
||||
connect(world, player, names, 'Sealed Caves (upper)', 'Sealed Caves (Xarion)', lambda state: state.has('Twin Pyramid Key', player) or state._timespinner_has_doublejump(world, player))
|
||||
connect(world, player, names, 'Sealed Caves (Xarion)', 'Sealed Caves (upper)', lambda state: state._timespinner_has_doublejump(world, player))
|
||||
connect(world, player, names, 'Sealed Caves (Xarion)', 'Space time continuum', lambda state: state.has('Twin Pyramid Key', player))
|
||||
connect(world, player, names, 'Refugee Camp', 'Forest')
|
||||
connect(world, player, names, 'Refugee Camp', 'Library', lambda state: not is_option_enabled(world, player, "Inverted"))
|
||||
@@ -114,9 +119,9 @@ def create_regions(world: MultiWorld, player: int, locations: Tuple[LocationData
|
||||
connect(world, player, names, 'Lower Lake Serene', 'Left Side forest Caves')
|
||||
connect(world, player, names, 'Lower Lake Serene', 'Caves of Banishment (upper)')
|
||||
connect(world, player, names, 'Caves of Banishment (upper)', 'Upper Lake Serene', lambda state: state.has('Water Mask', player))
|
||||
connect(world, player, names, 'Caves of Banishment (upper)', 'Caves of Banishment (Maw)', lambda state: state.has('Twin Pyramid Key', player) or state._timespinner_has_forwarddash_doublejump(world, player))
|
||||
connect(world, player, names, 'Caves of Banishment (upper)', 'Caves of Banishment (Maw)', lambda state: state.has('Twin Pyramid Key', player) or state._timespinner_has_doublejump(world, player))
|
||||
connect(world, player, names, 'Caves of Banishment (upper)', 'Space time continuum', lambda state: state.has('Twin Pyramid Key', player))
|
||||
connect(world, player, names, 'Caves of Banishment (Maw)', 'Caves of Banishment (upper)', lambda state: state._timespinner_has_forwarddash_doublejump(world, player))
|
||||
connect(world, player, names, 'Caves of Banishment (Maw)', 'Caves of Banishment (upper)', lambda state: state._timespinner_has_doublejump(world, player))
|
||||
connect(world, player, names, 'Caves of Banishment (Maw)', 'Caves of Banishment (Sirens)', lambda state: state.has('Gas Mask', player))
|
||||
connect(world, player, names, 'Caves of Banishment (Maw)', 'Space time continuum', lambda state: state.has('Twin Pyramid Key', player))
|
||||
connect(world, player, names, 'Caves of Banishment (Sirens)', 'Forest')
|
||||
@@ -150,6 +155,16 @@ def create_regions(world: MultiWorld, player: int, locations: Tuple[LocationData
|
||||
connect(world, player, names, 'Space time continuum', 'Caves of Banishment (upper)', lambda state: pyramid_keys_unlock == "GateCavesOfBanishment")
|
||||
|
||||
|
||||
def throwIfAnyLocationIsNotAssignedToARegion(regions: List[Region], regionNames: Set[str]):
|
||||
existingRegions = set()
|
||||
|
||||
for region in regions:
|
||||
existingRegions.add(region.name)
|
||||
|
||||
if (regionNames - existingRegions):
|
||||
raise Exception("Tiemspinner: the following regions are used in locations: {}, but no such region exists".format(regionNames - existingRegions))
|
||||
|
||||
|
||||
def create_location(player: int, location_data: LocationData, region: Region, location_cache: List[Location]) -> Location:
|
||||
location = Location(player, location_data.name, location_data.code, region)
|
||||
location.access_rule = location_data.rule
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
from typing import Dict, List, Set, TextIO
|
||||
from typing import Dict, List, Set, Tuple, TextIO
|
||||
from BaseClasses import Item, MultiWorld, Location
|
||||
from ..AutoWorld import World
|
||||
from .LogicMixin import TimespinnerLogic
|
||||
@@ -24,19 +24,31 @@ class TimespinnerWorld(World):
|
||||
location_name_to_id = {location.name: location.code for location in get_locations(None, None)}
|
||||
item_name_groups = get_item_names_per_category()
|
||||
|
||||
locked_locations: Dict[int, List[str]] = {}
|
||||
pyramid_keys_unlock: Dict[int, str] = {}
|
||||
location_cache: Dict[int, List[Location]] = {}
|
||||
locked_locations: List[str]
|
||||
pyramid_keys_unlock: str
|
||||
location_cache: List[Location]
|
||||
|
||||
def __init__(self, world: MultiWorld, player: int):
|
||||
super().__init__(world, player)
|
||||
|
||||
self.locked_locations = []
|
||||
self.location_cache = []
|
||||
self.pyramid_keys_unlock = get_pyramid_keys_unlock(world, player)
|
||||
|
||||
|
||||
def generate_early(self):
|
||||
self.locked_locations[self.player] = []
|
||||
self.location_cache[self.player] = []
|
||||
self.pyramid_keys_unlock[self.player] = get_pyramid_keys_unlock(self.world, self.player)
|
||||
# in generate_early the start_inventory isnt copied over to precollected_items yet, so we can still modify the options directly
|
||||
if self.world.start_inventory[self.player].value.pop('Meyef', 0) > 0:
|
||||
self.world.StartWithMeyef[self.player].value = self.world.StartWithMeyef[self.player].option_true
|
||||
if self.world.start_inventory[self.player].value.pop('Talaria Attachment', 0) > 0:
|
||||
self.world.QuickSeed[self.player].value = self.world.QuickSeed[self.player].option_true
|
||||
if self.world.start_inventory[self.player].value.pop('Jewelry Box', 0) > 0:
|
||||
self.world.StartWithJewelryBox[self.player].value = self.world.StartWithJewelryBox[self.player].option_true
|
||||
|
||||
|
||||
def create_regions(self):
|
||||
create_regions(self.world, self.player, get_locations(self.world, self.player),
|
||||
self.location_cache[self.player], self.pyramid_keys_unlock[self.player])
|
||||
self.location_cache, self.pyramid_keys_unlock)
|
||||
|
||||
|
||||
def create_item(self, name: str) -> Item:
|
||||
@@ -44,22 +56,22 @@ class TimespinnerWorld(World):
|
||||
|
||||
|
||||
def set_rules(self):
|
||||
setup_events(self.world, self.player, self.locked_locations[self.player], self.location_cache[self.player])
|
||||
setup_events(self.world, self.player, self.locked_locations, self.location_cache)
|
||||
|
||||
self.world.completion_condition[self.player] = lambda state: state.has('Killed Nightmare', self.player)
|
||||
|
||||
|
||||
def generate_basic(self):
|
||||
excluded_items = get_excluded_items_based_on_options(self.world, self.player)
|
||||
excluded_items = get_excluded_items(self, self.world, self.player)
|
||||
|
||||
assign_starter_items(self.world, self.player, excluded_items, self.locked_locations[self.player])
|
||||
assign_starter_items(self.world, self.player, excluded_items, self.locked_locations)
|
||||
|
||||
if not is_option_enabled(self.world, self.player, "QuickSeed") and not is_option_enabled(self.world, self.player, "Inverted"):
|
||||
place_first_progression_item(self.world, self.player, excluded_items, self.locked_locations[self.player])
|
||||
place_first_progression_item(self.world, self.player, excluded_items, self.locked_locations)
|
||||
|
||||
pool = get_item_pool(self.world, self.player, excluded_items)
|
||||
|
||||
fill_item_pool_with_dummy_items(self.world, self.player, self.locked_locations[self.player], self.location_cache[self.player], pool)
|
||||
fill_item_pool_with_dummy_items(self.world, self.player, self.locked_locations, self.location_cache, pool)
|
||||
|
||||
self.world.itempool += pool
|
||||
|
||||
@@ -73,17 +85,17 @@ class TimespinnerWorld(World):
|
||||
slot_data["StinkyMaw"] = True
|
||||
slot_data["ProgressiveVerticalMovement"] = False
|
||||
slot_data["ProgressiveKeycards"] = False
|
||||
slot_data["PyramidKeysGate"] = self.pyramid_keys_unlock[self.player]
|
||||
slot_data["PersonalItems"] = get_personal_items(self.player, self.location_cache[self.player])
|
||||
slot_data["PyramidKeysGate"] = self.pyramid_keys_unlock
|
||||
slot_data["PersonalItems"] = get_personal_items(self.player, self.location_cache)
|
||||
|
||||
return slot_data
|
||||
|
||||
|
||||
def write_spoiler_header(self, spoiler_handle: TextIO):
|
||||
spoiler_handle.write('Twin Pyramid Keys unlock: %s\n' % (self.pyramid_keys_unlock[self.player]))
|
||||
|
||||
spoiler_handle.write('Twin Pyramid Keys unlock: %s\n' % (self.pyramid_keys_unlock))
|
||||
|
||||
def get_excluded_items_based_on_options(world: MultiWorld, player: int) -> Set[str]:
|
||||
|
||||
def get_excluded_items(self: TimespinnerWorld, world: MultiWorld, player: int) -> Set[str]:
|
||||
excluded_items: Set[str] = set()
|
||||
|
||||
if is_option_enabled(world, player, "StartWithJewelryBox"):
|
||||
@@ -92,25 +104,47 @@ def get_excluded_items_based_on_options(world: MultiWorld, player: int) -> Set[s
|
||||
excluded_items.add('Meyef')
|
||||
if is_option_enabled(world, player, "QuickSeed"):
|
||||
excluded_items.add('Talaria Attachment')
|
||||
|
||||
for item in world.precollected_items[player]:
|
||||
if item.name not in self.item_name_groups['UseItem']:
|
||||
excluded_items.add(item.name)
|
||||
|
||||
return excluded_items
|
||||
|
||||
|
||||
def assign_starter_items(world: MultiWorld, player: int, excluded_items: Set[str], locked_locations: List[str]):
|
||||
melee_weapon = world.random.choice(starter_melee_weapons)
|
||||
spell = world.random.choice(starter_spells)
|
||||
non_local_items = world.non_local_items[player].value
|
||||
|
||||
excluded_items.add(melee_weapon)
|
||||
excluded_items.add(spell)
|
||||
local_starter_melee_weapons = tuple(item for item in starter_melee_weapons if item not in non_local_items)
|
||||
if not local_starter_melee_weapons:
|
||||
if 'Plasma Orb' in non_local_items:
|
||||
raise Exception("Atleast one melee orb must be local")
|
||||
else:
|
||||
local_starter_melee_weapons = ('Plasma Orb',)
|
||||
|
||||
melee_weapon_item = create_item_with_correct_settings(world, player, melee_weapon)
|
||||
spell_item = create_item_with_correct_settings(world, player, spell)
|
||||
local_starter_spells = tuple(item for item in starter_spells if item not in non_local_items)
|
||||
if not local_starter_spells:
|
||||
if 'Lightwall' in non_local_items:
|
||||
raise Exception("Atleast one spell must be local")
|
||||
else:
|
||||
local_starter_spells = ('Lightwall',)
|
||||
|
||||
world.get_location('Yo Momma 1', player).place_locked_item(melee_weapon_item)
|
||||
world.get_location('Yo Momma 2', player).place_locked_item(spell_item)
|
||||
assign_starter_item(world, player, excluded_items, locked_locations, 'Yo Momma 1', local_starter_melee_weapons)
|
||||
assign_starter_item(world, player, excluded_items, locked_locations, 'Yo Momma 2', local_starter_spells)
|
||||
|
||||
locked_locations.append('Yo Momma 1')
|
||||
locked_locations.append('Yo Momma 2')
|
||||
|
||||
def assign_starter_item(world: MultiWorld, player: int, excluded_items: Set[str], locked_locations: List[str],
|
||||
location: str, item_list: Tuple[str, ...]):
|
||||
|
||||
item_name = world.random.choice(item_list)
|
||||
|
||||
excluded_items.add(item_name)
|
||||
|
||||
item = create_item_with_correct_settings(world, player, item_name)
|
||||
|
||||
world.get_location(location, player).place_locked_item(item)
|
||||
|
||||
locked_locations.append(location)
|
||||
|
||||
|
||||
def get_item_pool(world: MultiWorld, player: int, excluded_items: Set[str]) -> List[Item]:
|
||||
@@ -133,8 +167,20 @@ def fill_item_pool_with_dummy_items(world: MultiWorld, player: int, locked_locat
|
||||
|
||||
|
||||
def place_first_progression_item(world: MultiWorld, player: int, excluded_items: Set[str], locked_locations: List[str]):
|
||||
progression_item = world.random.choice(starter_progression_items)
|
||||
location = world.random.choice(starter_progression_locations)
|
||||
for item in world.precollected_items[player]:
|
||||
if item.name in starter_progression_items:
|
||||
return
|
||||
|
||||
local_starter_progression_items = tuple(
|
||||
item for item in starter_progression_items if item not in world.non_local_items[player].value)
|
||||
non_excluded_starter_progression_locations = tuple(
|
||||
location for location in starter_progression_locations if location not in world.exclude_locations[player].value)
|
||||
|
||||
if not local_starter_progression_items or not non_excluded_starter_progression_locations:
|
||||
return
|
||||
|
||||
progression_item = world.random.choice(local_starter_progression_items)
|
||||
location = world.random.choice(non_excluded_starter_progression_locations)
|
||||
|
||||
excluded_items.add(progression_item)
|
||||
locked_locations.append(location)
|
||||
|
||||
Reference in New Issue
Block a user