Merge branch 'master' into owg_test

This commit is contained in:
Fabian Dill 2020-04-14 03:49:33 +02:00
commit c6a61e9008
8 changed files with 189 additions and 88 deletions

View File

@ -114,6 +114,20 @@ def parse_arguments(argv, no_defaults=False):
Timed mode. If time runs out, you lose (but can Timed mode. If time runs out, you lose (but can
still keep playing). still keep playing).
''') ''')
parser.add_argument('--dungeon_counters', default=defval('default'), const='default', nargs='?', choices=['default', 'on', 'pickup', 'off'],
help='''\
Select dungeon counter display settings. (default: %(default)s)
(Note, since timer takes up the same space on the hud as dungeon
counters, timer settings override dungeon counter settings.)
Default: Dungeon counters only show when the compass is
picked up, or otherwise sent, only when compass
shuffle is turned on.
On: Dungeon counters are always displayed.
Pickup: Dungeon counters are shown when the compass is
picked up, even when compass shuffle is turned
off.
Off: Dungeon counters are never shown.
''')
parser.add_argument('--progressive', default=defval('on'), const='normal', nargs='?', choices=['on', 'off', 'random'], parser.add_argument('--progressive', default=defval('on'), const='normal', nargs='?', choices=['on', 'off', 'random'],
help='''\ help='''\
Select progressive equipment setting. Affects available itempool. (default: %(default)s) Select progressive equipment setting. Affects available itempool. (default: %(default)s)
@ -288,6 +302,10 @@ def parse_arguments(argv, no_defaults=False):
ret = parser.parse_args(argv) ret = parser.parse_args(argv)
if ret.timer == "none": if ret.timer == "none":
ret.timer = False ret.timer = False
if ret.dungeon_counters == 'on':
ret.dungeon_counters = True
elif ret.dungeon_counters == 'off':
ret.dungeon_counters = False
if ret.keysanity: if ret.keysanity:
ret.mapshuffle, ret.compassshuffle, ret.keyshuffle, ret.bigkeyshuffle = [True] * 4 ret.mapshuffle, ret.compassshuffle, ret.keyshuffle, ret.bigkeyshuffle = [True] * 4
@ -303,7 +321,7 @@ def parse_arguments(argv, no_defaults=False):
'shufflebosses', 'shuffleenemies', 'enemy_health', 'enemy_damage', 'shufflepots', 'shufflebosses', 'shuffleenemies', 'enemy_health', 'enemy_damage', 'shufflepots',
'ow_palettes', 'uw_palettes', 'sprite', 'disablemusic', 'quickswap', 'fastmenu', 'heartcolor', 'ow_palettes', 'uw_palettes', 'sprite', 'disablemusic', 'quickswap', 'fastmenu', 'heartcolor',
'heartbeep', 'heartbeep',
'remote_items', 'progressive', 'extendedmsu']: 'remote_items', 'progressive', 'extendedmsu', 'dungeon_counters']:
value = getattr(defaults, name) if getattr(playerargs, name) is None else getattr(playerargs, name) value = getattr(defaults, name) if getattr(playerargs, name) is None else getattr(playerargs, name)
if player == 1: if player == 1:
setattr(ret, name, {1: value}) setattr(ret, name, {1: value})

16
Gui.py
View File

@ -289,6 +289,14 @@ def guiMain(args=None):
timerLabel = Label(timerFrame, text='Timer setting') timerLabel = Label(timerFrame, text='Timer setting')
timerLabel.pack(side=LEFT) timerLabel.pack(side=LEFT)
dungeonCounterFrame = Frame(drowDownFrame)
dungeonCounterVar = StringVar()
dungeonCounterVar.set('auto')
dungeonCounterOptionMenu = OptionMenu(dungeonCounterFrame, dungeonCounterVar, 'auto', 'off', 'on', 'on_compass_pickup')
dungeonCounterOptionMenu.pack(side=RIGHT)
dungeonCounterLabel = Label(dungeonCounterFrame, text='Dungeon Chest Counters')
dungeonCounterLabel.pack(side=LEFT)
progressiveFrame = Frame(drowDownFrame) progressiveFrame = Frame(drowDownFrame)
progressiveVar = StringVar() progressiveVar = StringVar()
progressiveVar.set('on') progressiveVar.set('on')
@ -330,6 +338,7 @@ def guiMain(args=None):
difficultyFrame.pack(expand=True, anchor=E) difficultyFrame.pack(expand=True, anchor=E)
itemfunctionFrame.pack(expand=True, anchor=E) itemfunctionFrame.pack(expand=True, anchor=E)
timerFrame.pack(expand=True, anchor=E) timerFrame.pack(expand=True, anchor=E)
dungeonCounterFrame.pack(expand=True, anchor=E)
progressiveFrame.pack(expand=True, anchor=E) progressiveFrame.pack(expand=True, anchor=E)
accessibilityFrame.pack(expand=True, anchor=E) accessibilityFrame.pack(expand=True, anchor=E)
algorithmFrame.pack(expand=True, anchor=E) algorithmFrame.pack(expand=True, anchor=E)
@ -427,6 +436,13 @@ def guiMain(args=None):
guiargs.timer = timerVar.get() guiargs.timer = timerVar.get()
if guiargs.timer == "none": if guiargs.timer == "none":
guiargs.timer = False guiargs.timer = False
guiargs.dungeon_counters = dungeonCounterVar.get()
if guiargs.dungeon_counters == "on_compass_pickup":
guiargs.dungeon_counters = "pickup"
elif guiargs.dungeon_counters == "on":
guiargs.dungeon_counters = True
elif guiargs.dungeon_counters == "off":
guiargs.dungeon_counters = False
guiargs.progressive = progressiveVar.get() guiargs.progressive = progressiveVar.get()
guiargs.accessibility = accessibilityVar.get() guiargs.accessibility = accessibilityVar.get()
guiargs.algorithm = algorithmVar.get() guiargs.algorithm = algorithmVar.get()

View File

@ -57,6 +57,7 @@ def main(args, seed=None):
world.timer = args.timer.copy() world.timer = args.timer.copy()
world.shufflepots = args.shufflepots.copy() world.shufflepots = args.shufflepots.copy()
world.progressive = args.progressive.copy() world.progressive = args.progressive.copy()
world.dungeon_counters = args.dungeon_counters.copy()
world.extendedmsu = args.extendedmsu.copy() world.extendedmsu = args.extendedmsu.copy()
world.rom_seeds = {player: random.randint(0, 999999999) for player in range(1, world.players + 1)} world.rom_seeds = {player: random.randint(0, 999999999) for player in range(1, world.players + 1)}

View File

@ -347,15 +347,20 @@ async def snes_connect(ctx : Context, address):
seen_problems.add(problem) seen_problems.add(problem)
logging.error(f"Error connecting to QUsb2snes ({problem})") logging.error(f"Error connecting to QUsb2snes ({problem})")
if len(seen_problems) == 1: if len(seen_problems) == 1:
#this is the first problem. Let's try launching QUsb2snes if it isn't already running # this is the first problem. Let's try launching QUsb2snes if it isn't already running
qusb2snes_path = Utils.get_options()["general_options"]["qusb2snes"] qusb2snes_path = Utils.get_options()["general_options"]["qusb2snes"]
import os import os
if not os.path.isfile(qusb2snes_path):
qusb2snes_path = Utils.local_path(qusb2snes_path)
if os.path.isfile(qusb2snes_path): if os.path.isfile(qusb2snes_path):
logging.info(f"Attempting to start {qusb2snes_path}") logging.info(f"Attempting to start {qusb2snes_path}")
import subprocess import subprocess
subprocess.Popen(qusb2snes_path, cwd=os.path.dirname(qusb2snes_path)) subprocess.Popen(qusb2snes_path, cwd=os.path.dirname(qusb2snes_path))
else: else:
logging.info(f"Attempt to start (Q)Usb2Snes was aborted as path {qusb2snes_path} was not found, please start it yourself if it is not running") logging.info(
f"Attempt to start (Q)Usb2Snes was aborted as path {qusb2snes_path} was not found, please start it yourself if it is not running")
await asyncio.sleep(1) await asyncio.sleep(1)
else: else:

View File

@ -6,6 +6,7 @@ import logging
import zlib import zlib
import collections import collections
import typing import typing
import inspect
import ModuleUpdate import ModuleUpdate
@ -66,6 +67,7 @@ class Context:
self.hints_used = collections.defaultdict(int) self.hints_used = collections.defaultdict(int)
self.hints_sent = collections.defaultdict(set) self.hints_sent = collections.defaultdict(set)
self.item_cheat = item_cheat self.item_cheat = item_cheat
self.running = True
def get_save(self) -> dict: def get_save(self) -> dict:
return { return {
@ -124,7 +126,7 @@ def notify_client(client: Client, text: str):
asyncio.create_task(send_msgs(client.socket, [['Print', text]])) asyncio.create_task(send_msgs(client.socket, [['Print', text]]))
# separated out, due to compatibilty between client's # separated out, due to compatibilty between clients
def notify_hints(ctx: Context, team: int, hints: typing.List[Utils.Hint]): def notify_hints(ctx: Context, team: int, hints: typing.List[Utils.Hint]):
cmd = [["Hint", hints]] cmd = [["Hint", hints]]
texts = [['Print', format_hint(ctx, team, hint)] for hint in hints] texts = [['Print', format_hint(ctx, team, hint)] for hint in hints]
@ -499,94 +501,145 @@ async def process_client_cmd(ctx: Context, client: Client, cmd, args):
notify_client(client, response) notify_client(client, response)
def set_password(ctx : Context, password): def set_password(ctx: Context, password):
ctx.password = password ctx.password = password
logging.warning('Password set to ' + password if password else 'Password disabled') logging.warning('Password set to ' + password if password else 'Password disabled')
class CommandProcessor():
commands: typing.Dict[str, typing.Callable]
def __init__(self):
self.commands = {name[5:].lower(): method for name, method in inspect.getmembers(self) if
name.startswith("_cmd_")}
def output(self, text: str):
print(text)
def __call__(self, raw: str):
if not raw:
return
command = raw.split()
basecommand = command[0]
if basecommand[0] == "/":
method = self.commands.get(basecommand[1:].lower(), None)
if not method:
self._error_unknown_command(basecommand[1:])
else:
method(*command[1:])
else:
self.default(raw)
def get_help_text(self) -> str:
s = ""
for command, method in self.commands.items():
spec = inspect.signature(method).parameters
s += f"/{command} {' '.join(spec)}\n {method.__doc__}\n"
return s
def _cmd_help(self):
"""Returns the help listing"""
self.output(self.get_help_text())
def default(self, raw: str):
self.output("Echo: " + raw)
def _error_unknown_command(self, raw: str):
self.output(f"Could not find command {raw}. Known commands: {', '.join(self.commands)}")
class ServerCommandProcessor(CommandProcessor):
ctx: Context
def __init__(self, ctx: Context):
self.ctx = ctx
super(ServerCommandProcessor, self).__init__()
def default(self, raw: str):
notify_all(self.ctx, '[Server]: ' + raw)
def _cmd_kick(self, player_name: str):
"""Kick specified player from the server"""
for client in self.ctx.clients:
if client.auth and client.name.lower() == player_name.lower() and client.socket and not client.socket.closed:
asyncio.create_task(client.socket.close())
self.output(f"Kicked {client.name}")
break
else:
self.output("Could not find player to kick")
def _cmd_players(self):
"""Get information about connected players"""
self.output(get_connected_players_string(self.ctx))
def _cmd_exit(self):
"""Shutdown the server"""
asyncio.create_task(self.ctx.server.ws_server._close())
self.ctx.running = False
def _cmd_password(self, new_password: str = ""):
"""Set the server password. Leave the password text empty to remove the password"""
set_password(self.ctx, new_password if new_password else None)
def _cmd_forfeit(self, player_name: str):
"""Send out the remaining items from a player's game to their intended recipients"""
seeked_player = player_name.lower()
for (team, slot), name in self.ctx.player_names.items():
if name.lower() == seeked_player:
forfeit_player(self.ctx, team, slot)
break
else:
self.output("Could not find player to forfeit")
def _cmd_send(self, player_name: str, *item_name: str):
"""Sends an item to the specified player"""
seeked_player, usable, response = get_intended_text(player_name, self.ctx.player_names.values())
if usable:
item = " ".join(item_name)
item, usable, response = get_intended_text(item, Items.item_table.keys())
if usable:
for client in self.ctx.clients:
if client.name == seeked_player:
new_item = ReceivedItem(Items.item_table[item][3], -1, client.slot)
get_received_items(self.ctx, client.team, client.slot).append(new_item)
notify_all(self.ctx, 'Cheat console: sending "' + item + '" to ' + client.name)
send_new_items(self.ctx)
return
else:
self.output(response)
else:
self.output(response)
def _cmd_hint(self, player_name: str, *item_or_location: str):
"""Send out a hint for a player's item or location to their team"""
seeked_player, usable, response = get_intended_text(player_name, self.ctx.player_names.values())
if usable:
for (team, slot), name in self.ctx.player_names.items():
if name == seeked_player:
item = " ".join(item_or_location)
item, usable, response = get_intended_text(item)
if usable:
if item in Items.item_table: # item name
hints = collect_hints(self.ctx, team, slot, item)
notify_hints(self.ctx, team, hints)
else: # location name
hints = collect_hints_location(self.ctx, team, slot, item)
notify_hints(self.ctx, team, hints)
else:
self.output(response)
return
else:
self.output(response)
async def console(ctx: Context): async def console(ctx: Context):
session = prompt_toolkit.PromptSession() session = prompt_toolkit.PromptSession()
running = True cmd_processor = ServerCommandProcessor(ctx)
while running: while ctx.running:
with patch_stdout(): with patch_stdout():
input_text = await session.prompt_async() input_text = await session.prompt_async()
try: try:
cmd_processor(input_text)
command = input_text.split()
if not command:
continue
if command[0] == '/exit':
await ctx.server.ws_server._close()
running = False
if command[0] == '/players':
logging.info(get_connected_players_string(ctx))
if command[0] == '/password':
set_password(ctx, command[1] if len(command) > 1 else None)
if command[0] == '/kick' and len(command) > 1:
team = int(command[2]) - 1 if len(command) > 2 and command[2].isdigit() else None
for client in ctx.clients:
if client.auth and client.name.lower() == command[1].lower() and (team is None or team == client.team):
if client.socket and not client.socket.closed:
await client.socket.close()
if command[0] == '/forfeitslot' and len(command) > 1 and command[1].isdigit():
if len(command) > 2 and command[2].isdigit():
team = int(command[1]) - 1
slot = int(command[2])
else:
team = 0
slot = int(command[1])
forfeit_player(ctx, team, slot)
if command[0] == '/forfeitplayer' and len(command) > 1:
seeked_player = command[1].lower()
for (team, slot), name in ctx.player_names.items():
if name.lower() == seeked_player:
forfeit_player(ctx, team, slot)
if command[0] == '/senditem':
if len(command) <= 2:
logging.info("Use /senditem {Playername} {itemname}\nFor example /senditem Berserker Lamp")
else:
seeked_player, usable, response = get_intended_text(command[1], ctx.player_names.values())
if usable:
item = " ".join(command[2:])
item, usable, response = get_intended_text(item, Items.item_table.keys())
if usable:
for client in ctx.clients:
if client.name == seeked_player:
new_item = ReceivedItem(Items.item_table[item][3], -1, client.slot)
get_received_items(ctx, client.team, client.slot).append(new_item)
notify_all(ctx, 'Cheat console: sending "' + item + '" to ' + client.name)
send_new_items(ctx)
else:
logging.warning(response)
else:
logging.warning(response)
if command[0] == '/hint':
if len(command) <= 2:
logging.info("Use /hint {Playername} {itemname/locationname}\nFor example /hint Berserker Lamp")
else:
seeked_player, usable, response = get_intended_text(command[1], ctx.player_names.values())
if usable:
for (team, slot), name in ctx.player_names.items():
if name == seeked_player:
item = " ".join(command[2:])
item, usable, response = get_intended_text(item)
if usable:
if item in Items.item_table: #item name
hints = collect_hints(ctx, team, slot, item)
notify_hints(ctx, team, hints)
else: #location name
hints = collect_hints_location(ctx, team, slot, item)
notify_hints(ctx, team, hints)
else:
logging.warning(response)
else:
logging.warning(response)
if command[0][0] != '/':
notify_all(ctx, '[Server]: ' + input_text)
except: except:
import traceback import traceback
traceback.print_exc() traceback.print_exc()
@ -702,4 +755,3 @@ async def main(args: argparse.Namespace):
if __name__ == '__main__': if __name__ == '__main__':
loop = asyncio.get_event_loop() loop = asyncio.get_event_loop()
loop.run_until_complete(main(parse_args())) loop.run_until_complete(main(parse_args()))
loop.close()

View File

@ -268,6 +268,8 @@ def roll_settings(weights):
'timed_countdown': 'timed-countdown', 'timed_countdown': 'timed-countdown',
'display': 'display'}[get_choice('timer', weights)] if 'timer' in weights.keys() else False 'display': 'display'}[get_choice('timer', weights)] if 'timer' in weights.keys() else False
ret.dungeon_counters = get_choice('dungeon_counters', weights) if 'dungeon_counters' in weights else 'default'
ret.progressive = convert_to_on_off(get_choice('progressive', weights)) if "progressive" in weights else 'on' ret.progressive = convert_to_on_off(get_choice('progressive', weights)) if "progressive" in weights else 'on'
inventoryweights = weights.get('startinventory', {}) inventoryweights = weights.get('startinventory', {})
startitems = [] startitems = []

10
Rom.py
View File

@ -1127,9 +1127,11 @@ def patch_rom(world, rom, player, team, enemized):
rom.write_byte(0x18003B, 0x01 if world.mapshuffle[player] else 0x00) # maps showing crystals on overworld rom.write_byte(0x18003B, 0x01 if world.mapshuffle[player] else 0x00) # maps showing crystals on overworld
# compasses showing dungeon count # compasses showing dungeon count
if world.clock_mode[player]: if world.clock_mode[player] or not world.dungeon_counters[player]:
rom.write_byte(0x18003C, 0x00) # Currently must be off if timer is on, because they use same HUD location rom.write_byte(0x18003C, 0x00) # Currently must be off if timer is on, because they use same HUD location
elif world.compassshuffle[player]: elif world.dungeon_counters[player] == True:
rom.write_byte(0x18003C, 0x02) # always on
elif world.compassshuffle[player] or world.dungeon_counters[player] == 'pickup':
rom.write_byte(0x18003C, 0x01) # show on pickup rom.write_byte(0x18003C, 0x01) # show on pickup
else: else:
rom.write_byte(0x18003C, 0x00) rom.write_byte(0x18003C, 0x00)
@ -1144,8 +1146,8 @@ def patch_rom(world, rom, player, team, enemized):
# #
rom.write_byte(0x180045, ((0x01 if world.keyshuffle[player] else 0x00) rom.write_byte(0x180045, ((0x01 if world.keyshuffle[player] else 0x00)
| (0x02 if world.bigkeyshuffle[player] else 0x00) | (0x02 if world.bigkeyshuffle[player] else 0x00)
| (0x04 if world.compassshuffle[player] else 0x00) | (0x04 if world.mapshuffle[player] else 0x00)
| (0x08 if world.mapshuffle[player] else 0x00))) # free roaming items in menu | (0x08 if world.compassshuffle[player] else 0x00))) # free roaming items in menu
# Map reveals # Map reveals
reveal_bytes = { reveal_bytes = {

View File

@ -46,6 +46,11 @@ dungeon_items: # alternative to the 4 shuffles above this, does nothing until th
mc: 0 # shuffle Maps and Compass mc: 0 # shuffle Maps and Compass
none: 1 # shuffle none of the 4 none: 1 # shuffle none of the 4
mcsb: 0 # shuffle all of the 4, any combination of m, c, s and b will shuffle the respective item, or not if it's missing, so you can add more options here mcsb: 0 # shuffle all of the 4, any combination of m, c, s and b will shuffle the respective item, or not if it's missing, so you can add more options here
dungeon_counters:
on: 0 # always display amount of items checked in a dungeon
pickup: 0 # show when compass is picked up
default: 1 # show when compass is picked up if the compass itself is shuffled
off: 0 # never show item count in dungeons
accessibility: accessibility:
items: 0 # Guarantees you will be able to acquire all items, but you may not be able to access all locations items: 0 # Guarantees you will be able to acquire all items, but you may not be able to access all locations
locations: 1 # Guarantees you will be able to access all locations, and therefore all items locations: 1 # Guarantees you will be able to access all locations, and therefore all items