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
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'],
help='''\
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)
if ret.timer == "none":
ret.timer = False
if ret.dungeon_counters == 'on':
ret.dungeon_counters = True
elif ret.dungeon_counters == 'off':
ret.dungeon_counters = False
if ret.keysanity:
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',
'ow_palettes', 'uw_palettes', 'sprite', 'disablemusic', 'quickswap', 'fastmenu', 'heartcolor',
'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)
if player == 1:
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.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)
progressiveVar = StringVar()
progressiveVar.set('on')
@ -330,6 +338,7 @@ def guiMain(args=None):
difficultyFrame.pack(expand=True, anchor=E)
itemfunctionFrame.pack(expand=True, anchor=E)
timerFrame.pack(expand=True, anchor=E)
dungeonCounterFrame.pack(expand=True, anchor=E)
progressiveFrame.pack(expand=True, anchor=E)
accessibilityFrame.pack(expand=True, anchor=E)
algorithmFrame.pack(expand=True, anchor=E)
@ -427,6 +436,13 @@ def guiMain(args=None):
guiargs.timer = timerVar.get()
if guiargs.timer == "none":
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.accessibility = accessibilityVar.get()
guiargs.algorithm = algorithmVar.get()

View File

@ -57,6 +57,7 @@ def main(args, seed=None):
world.timer = args.timer.copy()
world.shufflepots = args.shufflepots.copy()
world.progressive = args.progressive.copy()
world.dungeon_counters = args.dungeon_counters.copy()
world.extendedmsu = args.extendedmsu.copy()
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)
logging.error(f"Error connecting to QUsb2snes ({problem})")
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"]
import os
if not os.path.isfile(qusb2snes_path):
qusb2snes_path = Utils.local_path(qusb2snes_path)
if os.path.isfile(qusb2snes_path):
logging.info(f"Attempting to start {qusb2snes_path}")
import subprocess
subprocess.Popen(qusb2snes_path, cwd=os.path.dirname(qusb2snes_path))
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)
else:

View File

@ -6,6 +6,7 @@ import logging
import zlib
import collections
import typing
import inspect
import ModuleUpdate
@ -66,6 +67,7 @@ class Context:
self.hints_used = collections.defaultdict(int)
self.hints_sent = collections.defaultdict(set)
self.item_cheat = item_cheat
self.running = True
def get_save(self) -> dict:
return {
@ -124,7 +126,7 @@ def notify_client(client: Client, text: str):
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]):
cmd = [["Hint", 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)
def set_password(ctx : Context, password):
def set_password(ctx: Context, password):
ctx.password = password
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):
session = prompt_toolkit.PromptSession()
running = True
while running:
cmd_processor = ServerCommandProcessor(ctx)
while ctx.running:
with patch_stdout():
input_text = await session.prompt_async()
try:
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)
cmd_processor(input_text)
except:
import traceback
traceback.print_exc()
@ -702,4 +755,3 @@ async def main(args: argparse.Namespace):
if __name__ == '__main__':
loop = asyncio.get_event_loop()
loop.run_until_complete(main(parse_args()))
loop.close()

View File

@ -268,6 +268,8 @@ def roll_settings(weights):
'timed_countdown': 'timed-countdown',
'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'
inventoryweights = weights.get('startinventory', {})
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
# 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
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
else:
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)
| (0x02 if world.bigkeyshuffle[player] else 0x00)
| (0x04 if world.compassshuffle[player] else 0x00)
| (0x08 if world.mapshuffle[player] else 0x00))) # free roaming items in menu
| (0x04 if world.mapshuffle[player] else 0x00)
| (0x08 if world.compassshuffle[player] else 0x00))) # free roaming items in menu
# Map reveals
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
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
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:
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