implement server-client handshake and move hint system to optional colorama support

This commit is contained in:
Fabian Dill 2020-02-16 15:32:40 +01:00
parent 0986b36b39
commit b04db006e0
5 changed files with 275 additions and 116 deletions

View File

@ -8,7 +8,6 @@ from EntranceShuffle import door_addresses
from Utils import int16_as_bytes from Utils import int16_as_bytes
class World(object): class World(object):
def __init__(self, players, shuffle, logic, mode, swords, difficulty, difficulty_adjustments, timer, progressive, goal, algorithm, accessibility, shuffle_ganon, retro, custom, customitemarray, hints): def __init__(self, players, shuffle, logic, mode, swords, difficulty, difficulty_adjustments, timer, progressive, goal, algorithm, accessibility, shuffle_ganon, retro, custom, customitemarray, hints):
self.players = players self.players = players
self.teams = 1 self.teams = 1
@ -953,7 +952,7 @@ class Shop(object):
class Spoiler(object): class Spoiler(object):
world: World
def __init__(self, world): def __init__(self, world):
self.world = world self.world = world
self.hashes = {} self.hashes = {}

141
Items.py
View File

@ -51,39 +51,100 @@ item_table = {'Bow': (True, False, None, 0x0B, 'You have\nchosen the\narcher cla
'Bottle (Red Potion)': (True, False, None, 0x2B, 'Hearty red goop!', 'and the red goo', 'the liquid kid', 'potion for sale', 'free samples', 'bottle boy has red goo again', 'a Bottle'), 'Bottle (Red Potion)': (True, False, None, 0x2B, 'Hearty red goop!', 'and the red goo', 'the liquid kid', 'potion for sale', 'free samples', 'bottle boy has red goo again', 'a Bottle'),
'Bottle (Green Potion)': (True, False, None, 0x2C, 'Refreshing green goop!', 'and the green goo', 'the liquid kid', 'potion for sale', 'free samples', 'bottle boy has green goo again', 'a Bottle'), 'Bottle (Green Potion)': (True, False, None, 0x2C, 'Refreshing green goop!', 'and the green goo', 'the liquid kid', 'potion for sale', 'free samples', 'bottle boy has green goo again', 'a Bottle'),
'Bottle (Blue Potion)': (True, False, None, 0x2D, 'Delicious blue goop!', 'and the blue goo', 'the liquid kid', 'potion for sale', 'free samples', 'bottle boy has blue goo again', 'a Bottle'), 'Bottle (Blue Potion)': (True, False, None, 0x2D, 'Delicious blue goop!', 'and the blue goo', 'the liquid kid', 'potion for sale', 'free samples', 'bottle boy has blue goo again', 'a Bottle'),
'Bottle (Fairy)': (True, False, None, 0x3D, 'Save me and I will revive you', 'and the captive', 'the tingle kid', 'hostage for sale', 'fairy dust and shrooms', 'bottle boy has friend again', 'a Bottle'), 'Bottle (Fairy)': (
'Bottle (Bee)': (True, False, None, 0x3C, 'I will sting your foes a few times', 'and the sting buddy', 'the beekeeper kid', 'insect for sale', 'shroom pollenation', 'bottle boy has mad bee again', 'a Bottle'), True, False, None, 0x3D, 'Save me and I will revive you', 'and the captive', 'the tingle kid',
'Bottle (Good Bee)': (True, False, None, 0x48, 'I will sting your foes a whole lot!', 'and the sparkle sting', 'the beekeeper kid', 'insect for sale', 'shroom pollenation', 'bottle boy has beetor again', 'a Bottle'), 'hostage for sale', 'fairy dust and shrooms', 'bottle boy has friend again', 'a Bottle'),
'Master Sword': (True, False, 'Sword', 0x50, 'I beat barries and pigs alike', 'and the master sword', 'sword-wielding kid', 'glow sword for sale', 'fungus for blue slasher', 'sword boy fights again', 'the Master Sword'), 'Bottle (Bee)': (
'Tempered Sword': (True, False, 'Sword', 0x02, 'I stole the\nblacksmith\'s\njob!', 'the tempered sword', 'sword-wielding kid', 'flame sword for sale', 'fungus for red slasher', 'sword boy fights again', 'the Tempered Sword'), True, False, None, 0x3C, 'I will sting your foes a few times', 'and the sting buddy', 'the beekeeper kid',
'Fighter Sword': (True, False, 'Sword', 0x49, 'A pathetic\nsword rests\nhere!', 'the tiny sword', 'sword-wielding kid', 'tiny sword for sale', 'fungus for tiny slasher', 'sword boy fights again', 'the small sword'), 'insect for sale', 'shroom pollenation', 'bottle boy has mad bee again', 'a Bottle'),
'Golden Sword': (True, False, 'Sword', 0x03, 'The butter\nsword rests\nhere!', 'and the butter sword', 'sword-wielding kid', 'butter for sale', 'cap churned to butter', 'sword boy fights again', 'the Golden Sword'), 'Bottle (Good Bee)': (
'Progressive Sword': (True, False, 'Sword', 0x5E, 'a better copy\nof your sword\nfor your time', 'the unknown sword', 'sword-wielding kid', 'sword for sale', 'fungus for some slasher', 'sword boy fights again', 'a sword'), True, False, None, 0x48, 'I will sting your foes a whole lot!', 'and the sparkle sting',
'Progressive Glove': (True, False, None, 0x61, 'a way to lift\nheavier things', 'and the lift upgrade', 'body-building kid', 'some glove for sale', 'fungus for gloves', 'body-building boy lifts again', 'a glove'), 'the beekeeper kid', 'insect for sale', 'shroom pollenation', 'bottle boy has beetor again', 'a Bottle'),
'Silver Arrows': (True, False, None, 0x58, 'Do you fancy\nsilver tipped\narrows?', 'and the ganonsbane', 'ganon-killing kid', 'ganon doom for sale', 'fungus for pork', 'archer boy shines again', 'the silver arrows'), 'Master Sword': (
'Green Pendant': (True, False, 'Crystal', [0x04, 0x38, 0x62, 0x00, 0x69, 0x01], None, None, None, None, None, None, None), True, False, 'Sword', 0x50, 'I beat barries and pigs alike', 'and the master sword', 'sword-wielding kid',
'Red Pendant': (True, False, 'Crystal', [0x02, 0x34, 0x60, 0x00, 0x69, 0x02], None, None, None, None, None, None, None), 'glow sword for sale', 'fungus for blue slasher', 'sword boy fights again', 'the Master Sword'),
'Blue Pendant': (True, False, 'Crystal', [0x01, 0x32, 0x60, 0x00, 0x69, 0x03], None, None, None, None, None, None, None), 'Tempered Sword': (True, False, 'Sword', 0x02, 'I stole the\nblacksmith\'s\njob!', 'the tempered sword',
'Triforce': (True, False, None, 0x6A, '\n YOU WIN!', 'and the triforce', 'victorious kid', 'victory for sale', 'fungus for the win', 'greedy boy wins game again', 'the Triforce'), 'sword-wielding kid', 'flame sword for sale', 'fungus for red slasher',
'Power Star': (True, False, None, 0x6B, 'a small victory', 'and the power star', 'star-struck kid', 'star for sale', 'see stars with shroom', 'mario powers up again', 'a Power Star'), 'sword boy fights again', 'the Tempered Sword'),
'Triforce Piece': (True, False, None, 0x6C, 'a small victory', 'and the thirdforce', 'triangular kid', 'triangle for sale', 'fungus for triangle', 'wise boy has triangle again', 'a Triforce Piece'), 'Fighter Sword': (
'Crystal 1': (True, False, 'Crystal', [0x02, 0x34, 0x64, 0x40, 0x7F, 0x06], None, None, None, None, None, None, None), True, False, 'Sword', 0x49, 'A pathetic\nsword rests\nhere!', 'the tiny sword', 'sword-wielding kid',
'Crystal 2': (True, False, 'Crystal', [0x10, 0x34, 0x64, 0x40, 0x79, 0x06], None, None, None, None, None, None, None), 'tiny sword for sale', 'fungus for tiny slasher', 'sword boy fights again', 'the small sword'),
'Crystal 3': (True, False, 'Crystal', [0x40, 0x34, 0x64, 0x40, 0x6C, 0x06], None, None, None, None, None, None, None), 'Golden Sword': (True, False, 'Sword', 0x03, 'The butter\nsword rests\nhere!', 'and the butter sword',
'Crystal 4': (True, False, 'Crystal', [0x20, 0x34, 0x64, 0x40, 0x6D, 0x06], None, None, None, None, None, None, None), 'sword-wielding kid', 'butter for sale', 'cap churned to butter',
'Crystal 5': (True, False, 'Crystal', [0x04, 0x32, 0x64, 0x40, 0x6E, 0x06], None, None, None, None, None, None, None), 'sword boy fights again', 'the Golden Sword'),
'Crystal 6': (True, False, 'Crystal', [0x01, 0x32, 0x64, 0x40, 0x6F, 0x06], None, None, None, None, None, None, None), 'Progressive Sword': (
'Crystal 7': (True, False, 'Crystal', [0x08, 0x34, 0x64, 0x40, 0x7C, 0x06], None, None, None, None, None, None, None), True, False, 'Sword', 0x5E, 'a better copy\nof your sword\nfor your time', 'the unknown sword',
'Single Arrow': (False, False, None, 0x43, 'a lonely arrow\nsits here.', 'and the arrow', 'stick-collecting kid', 'sewing needle for sale', 'fungus for arrow', 'archer boy sews again', 'an arrow'), 'sword-wielding kid', 'sword for sale', 'fungus for some slasher', 'sword boy fights again', 'a sword'),
'Arrows (10)': (False, False, None, 0x44, 'This will give\nyou ten shots\nwith your bow!', 'and the arrow pack', 'stick-collecting kid', 'sewing kit for sale', 'fungus for arrows', 'archer boy sews again', 'ten arrows'), 'Progressive Glove': (
'Arrow Upgrade (+10)': (False, False, None, 0x54, 'increase arrow\nstorage, low\nlow price', 'and the quiver', 'quiver-enlarging kid', 'arrow boost for sale', 'witch and more skewers', 'upgrade boy sews more again', 'arrow capacity'), True, False, None, 0x61, 'a way to lift\nheavier things', 'and the lift upgrade', 'body-building kid',
'Arrow Upgrade (+5)': (False, False, None, 0x53, 'increase arrow\nstorage, low\nlow price', 'and the quiver', 'quiver-enlarging kid', 'arrow boost for sale', 'witch and more skewers', 'upgrade boy sews more again', 'arrow capacity'), 'some glove for sale', 'fungus for gloves', 'body-building boy lifts again', 'a glove'),
'Single Bomb': (False, False, None, 0x27, 'I make things\ngo BOOM! But\njust once.', 'and the explosion', 'the bomb-holding kid', 'firecracker for sale', 'blend fungus into bomb', '\'splosion boy explodes again', 'a bomb'), 'Silver Arrows': (True, False, None, 0x58, 'Do you fancy\nsilver tipped\narrows?', 'and the ganonsbane',
'Bombs (3)': (False, False, None, 0x28, 'I make things\ngo triple\nBOOM!!!', 'and the explosions', 'the bomb-holding kid', 'firecrackers for sale', 'blend fungus into bombs', '\'splosion boy explodes again', 'three bombs'), 'ganon-killing kid', 'ganon doom for sale', 'fungus for pork',
'Bombs (10)': (False, False, None, 0x31, 'I make things\ngo BOOM! Ten\ntimes!', 'and the explosions', 'the bomb-holding kid', 'firecrackers for sale', 'blend fungus into bombs', '\'splosion boy explodes again', 'ten bombs'), 'archer boy shines again', 'the silver arrows'),
'Bomb Upgrade (+10)': (False, False, None, 0x52, 'increase bomb\nstorage, low\nlow price', 'and the bomb bag', 'boom-enlarging kid', 'bomb boost for sale', 'the shroom goes boom', 'upgrade boy explodes more again', 'bomb capacity'), 'Green Pendant': (
'Bomb Upgrade (+5)': (False, False, None, 0x51, 'increase bomb\nstorage, low\nlow price', 'and the bomb bag', 'boom-enlarging kid', 'bomb boost for sale', 'the shroom goes boom', 'upgrade boy explodes more again', 'bomb capacity'), True, False, 'Crystal', (0x04, 0x38, 0x62, 0x00, 0x69, 0x01), None, None, None, None, None, None, None),
'Blue Mail': (False, True, None, 0x22, 'Now you\'re a\nblue elf!', 'and the banana hat', 'the protected kid', 'banana hat for sale', 'the clothing store', 'tailor boy banana hatted again', 'the blue mail'), 'Red Pendant': (
True, False, 'Crystal', (0x02, 0x34, 0x60, 0x00, 0x69, 0x02), None, None, None, None, None, None, None),
'Blue Pendant': (
True, False, 'Crystal', (0x01, 0x32, 0x60, 0x00, 0x69, 0x03), None, None, None, None, None, None, None),
'Triforce': (
True, False, None, 0x6A, '\n YOU WIN!', 'and the triforce', 'victorious kid', 'victory for sale',
'fungus for the win', 'greedy boy wins game again', 'the Triforce'),
'Power Star': (
True, False, None, 0x6B, 'a small victory', 'and the power star', 'star-struck kid', 'star for sale',
'see stars with shroom', 'mario powers up again', 'a Power Star'),
'Triforce Piece': (
True, False, None, 0x6C, 'a small victory', 'and the thirdforce', 'triangular kid', 'triangle for sale',
'fungus for triangle', 'wise boy has triangle again', 'a Triforce Piece'),
'Crystal 1': (
True, False, 'Crystal', (0x02, 0x34, 0x64, 0x40, 0x7F, 0x06), None, None, None, None, None, None, None),
'Crystal 2': (
True, False, 'Crystal', (0x10, 0x34, 0x64, 0x40, 0x79, 0x06), None, None, None, None, None, None, None),
'Crystal 3': (
True, False, 'Crystal', (0x40, 0x34, 0x64, 0x40, 0x6C, 0x06), None, None, None, None, None, None, None),
'Crystal 4': (
True, False, 'Crystal', (0x20, 0x34, 0x64, 0x40, 0x6D, 0x06), None, None, None, None, None, None, None),
'Crystal 5': (
True, False, 'Crystal', (0x04, 0x32, 0x64, 0x40, 0x6E, 0x06), None, None, None, None, None, None, None),
'Crystal 6': (
True, False, 'Crystal', (0x01, 0x32, 0x64, 0x40, 0x6F, 0x06), None, None, None, None, None, None, None),
'Crystal 7': (
True, False, 'Crystal', (0x08, 0x34, 0x64, 0x40, 0x7C, 0x06), None, None, None, None, None, None, None),
'Single Arrow': (
False, False, None, 0x43, 'a lonely arrow\nsits here.', 'and the arrow', 'stick-collecting kid',
'sewing needle for sale', 'fungus for arrow', 'archer boy sews again', 'an arrow'),
'Arrows (10)': (
False, False, None, 0x44, 'This will give\nyou ten shots\nwith your bow!', 'and the arrow pack',
'stick-collecting kid', 'sewing kit for sale', 'fungus for arrows', 'archer boy sews again',
'ten arrows'),
'Arrow Upgrade (+10)': (
False, False, None, 0x54, 'increase arrow\nstorage, low\nlow price', 'and the quiver',
'quiver-enlarging kid', 'arrow boost for sale', 'witch and more skewers', 'upgrade boy sews more again',
'arrow capacity'),
'Arrow Upgrade (+5)': (
False, False, None, 0x53, 'increase arrow\nstorage, low\nlow price', 'and the quiver',
'quiver-enlarging kid', 'arrow boost for sale', 'witch and more skewers', 'upgrade boy sews more again',
'arrow capacity'),
'Single Bomb': (False, False, None, 0x27, 'I make things\ngo BOOM! But\njust once.', 'and the explosion',
'the bomb-holding kid', 'firecracker for sale', 'blend fungus into bomb',
'\'splosion boy explodes again', 'a bomb'),
'Bombs (3)': (False, False, None, 0x28, 'I make things\ngo triple\nBOOM!!!', 'and the explosions',
'the bomb-holding kid', 'firecrackers for sale', 'blend fungus into bombs',
'\'splosion boy explodes again', 'three bombs'),
'Bombs (10)': (False, False, None, 0x31, 'I make things\ngo BOOM! Ten\ntimes!', 'and the explosions',
'the bomb-holding kid', 'firecrackers for sale', 'blend fungus into bombs',
'\'splosion boy explodes again', 'ten bombs'),
'Bomb Upgrade (+10)': (
False, False, None, 0x52, 'increase bomb\nstorage, low\nlow price', 'and the bomb bag',
'boom-enlarging kid', 'bomb boost for sale', 'the shroom goes boom', 'upgrade boy explodes more again',
'bomb capacity'),
'Bomb Upgrade (+5)': (
False, False, None, 0x51, 'increase bomb\nstorage, low\nlow price', 'and the bomb bag',
'boom-enlarging kid', 'bomb boost for sale', 'the shroom goes boom', 'upgrade boy explodes more again',
'bomb capacity'),
'Blue Mail': (
False, True, None, 0x22, 'Now you\'re a\nblue elf!', 'and the banana hat', 'the protected kid',
'banana hat for sale', 'the clothing store', 'tailor boy banana hatted again', 'the blue mail'),
'Red Mail': (False, True, None, 0x23, 'Now you\'re a\nred elf!', 'and the eggplant hat', 'well-protected kid', 'purple hat for sale', 'the nice clothing store', 'tailor boy fears nothing again', 'the red mail'), 'Red Mail': (False, True, None, 0x23, 'Now you\'re a\nred elf!', 'and the eggplant hat', 'well-protected kid', 'purple hat for sale', 'the nice clothing store', 'tailor boy fears nothing again', 'the red mail'),
'Progressive Armor': (False, True, None, 0x60, 'time for a\nchange of\nclothes?', 'and the unknown hat', 'the protected kid', 'new hat for sale', 'the clothing store', 'tailor boy has threads again', 'some armor'), 'Progressive Armor': (False, True, None, 0x60, 'time for a\nchange of\nclothes?', 'and the unknown hat', 'the protected kid', 'new hat for sale', 'the clothing store', 'tailor boy has threads again', 'some armor'),
'Blue Boomerang': (True, False, None, 0x0C, 'No matter what\nyou do, blue\nreturns to you', 'and the bluemarang', 'the bat-throwing kid', 'bent stick for sale', 'fungus for puma-stick', 'throwing boy plays fetch again', 'the blue boomerang'), 'Blue Boomerang': (True, False, None, 0x0C, 'No matter what\nyou do, blue\nreturns to you', 'and the bluemarang', 'the bat-throwing kid', 'bent stick for sale', 'fungus for puma-stick', 'throwing boy plays fetch again', 'the blue boomerang'),
@ -166,12 +227,20 @@ item_table = {'Bow': (True, False, None, 0x0B, 'You have\nchosen the\narcher cla
'Red Potion': (False, False, None, 0x2E, 'Hearty red goop!', 'and the red goo', 'the liquid kid', 'potion for sale', 'free samples', 'bottle boy has red goo again', 'a red potion'), 'Red Potion': (False, False, None, 0x2E, 'Hearty red goop!', 'and the red goo', 'the liquid kid', 'potion for sale', 'free samples', 'bottle boy has red goo again', 'a red potion'),
'Green Potion': (False, False, None, 0x2F, 'Refreshing green goop!', 'and the green goo', 'the liquid kid', 'potion for sale', 'free samples', 'bottle boy has green goo again', 'a green potion'), 'Green Potion': (False, False, None, 0x2F, 'Refreshing green goop!', 'and the green goo', 'the liquid kid', 'potion for sale', 'free samples', 'bottle boy has green goo again', 'a green potion'),
'Blue Potion': (False, False, None, 0x30, 'Delicious blue goop!', 'and the blue goo', 'the liquid kid', 'potion for sale', 'free samples', 'bottle boy has blue goo again', 'a blue potion'), 'Blue Potion': (False, False, None, 0x30, 'Delicious blue goop!', 'and the blue goo', 'the liquid kid', 'potion for sale', 'free samples', 'bottle boy has blue goo again', 'a blue potion'),
'Bee': (False, False, None, 0x0E, 'I will sting your foes a few times', 'and the sting buddy', 'the beekeeper kid', 'insect for sale', 'shroom pollenation', 'bottle boy has mad bee again', 'a bee'), 'Bee': (False, False, None, 0x0E, 'I will sting your foes a few times', 'and the sting buddy',
'Small Heart': (False, False, None, 0x42, 'Just a little\npiece of love!', 'and the heart', 'the life-giving kid', 'little love for sale', 'fungus for life', 'life boy feels some love again', 'a heart'), 'the beekeeper kid', 'insect for sale', 'shroom pollenation', 'bottle boy has mad bee again',
'a bee'),
'Small Heart': (
False, False, None, 0x42, 'Just a little\npiece of love!', 'and the heart', 'the life-giving kid',
'little love for sale', 'fungus for life', 'life boy feels some love again', 'a heart'),
'Beat Agahnim 1': (True, False, 'Event', None, None, None, None, None, None, None, None), 'Beat Agahnim 1': (True, False, 'Event', None, None, None, None, None, None, None, None),
'Beat Agahnim 2': (True, False, 'Event', None, None, None, None, None, None, None, None), 'Beat Agahnim 2': (True, False, 'Event', None, None, None, None, None, None, None, None),
'Get Frog': (True, False, 'Event', None, None, None, None, None, None, None, None), 'Get Frog': (True, False, 'Event', None, None, None, None, None, None, None, None),
'Return Smith': (True, False, 'Event', None, None, None, None, None, None, None, None), 'Return Smith': (True, False, 'Event', None, None, None, None, None, None, None, None),
'Pick Up Purple Chest': (True, False, 'Event', None, None, None, None, None, None, None, None), 'Pick Up Purple Chest': (True, False, 'Event', None, None, None, None, None, None, None, None),
'Open Floodgate': (True, False, 'Event', None, None, None, None, None, None, None, None), 'Open Floodgate': (True, False, 'Event', None, None, None, None, None, None, None, None),
} }
lookup_lower_name_to_id = {name.lower(): data[3] for name, data in item_table.items()}
lookup_lower_name_to_name = {name.lower(): name for name in item_table}
lookup_id_to_name = {data[3]: name for name, data in item_table.items()}

View File

@ -17,6 +17,7 @@ import aioconsole
import Items import Items
import Regions import Regions
import Utils
class ReceivedItem: class ReceivedItem:
def __init__(self, item, location, player): def __init__(self, item, location, player):
@ -59,15 +60,20 @@ class Context:
self.rom = None self.rom = None
self.auth = None self.auth = None
color_codes = {'reset': 0, 'bold': 1, 'underline': 4, 'black': 30, 'red': 31, 'green': 32, 'yellow': 33, 'blue': 34,
'magenta': 35, 'cyan': 36, 'white': 37, 'black_bg': 40, 'red_bg': 41, 'green_bg': 42, 'yellow_bg': 43,
'blue_bg': 44, 'purple_bg': 45, 'cyan_bg': 46, 'white_bg': 47}
def color_code(*args): def color_code(*args):
codes = {'reset': 0, 'bold': 1, 'underline': 4, 'black': 30, 'red': 31, 'green': 32, 'yellow': 33, 'blue': 34, return '\033[' + ';'.join([str(color_codes[arg]) for arg in args]) + 'm'
'magenta': 35, 'cyan': 36, 'white': 37 , 'black_bg': 40, 'red_bg': 41, 'green_bg': 42, 'yellow_bg': 43,
'blue_bg': 44, 'purple_bg': 45, 'cyan_bg': 46, 'white_bg': 47}
return '\033[' + ';'.join([str(codes[arg]) for arg in args]) + 'm'
def color(text, *args): def color(text, *args):
return color_code(*args) + text + color_code('reset') return color_code(*args) + text + color_code('reset')
RECONNECT_DELAY = 30 RECONNECT_DELAY = 30
ROM_START = 0x000000 ROM_START = 0x000000
@ -604,15 +610,17 @@ async def process_server_cmd(ctx : Context, cmd, args):
logging.info('--------------------------------') logging.info('--------------------------------')
logging.info('Room Information:') logging.info('Room Information:')
logging.info('--------------------------------') logging.info('--------------------------------')
logging.info(f'Server protocol version: {args.get("version", "unknown Bonta Protocol")}')
if "tags" in args:
logging.info("Server protocol tags: " + ", ".join(args["tags"]))
if args['password']: if args['password']:
logging.info('Password required') logging.info('Password required')
if len(args['players']) < 1: if len(args['players']) < 1:
logging.info('No player connected') logging.info('No player connected')
else: else:
args['players'].sort() args['players'].sort()
current_team = 0 current_team = -1
logging.info('Connected players:') logging.info('Connected players:')
logging.info(' Team #1')
for team, slot, name in args['players']: for team, slot, name in args['players']:
if team != current_team: if team != current_team:
logging.info(f' Team #{team + 1}') logging.info(f' Team #{team + 1}')
@ -620,18 +628,21 @@ async def process_server_cmd(ctx : Context, cmd, args):
logging.info(' %s (Player %d)' % (name, slot)) logging.info(' %s (Player %d)' % (name, slot))
await server_auth(ctx, args['password']) await server_auth(ctx, args['password'])
if cmd == 'ConnectionRefused': elif cmd == 'ConnectionRefused':
if 'InvalidPassword' in args: if 'InvalidPassword' in args:
logging.error('Invalid password') logging.error('Invalid password')
ctx.password = None ctx.password = None
await server_auth(ctx, True) await server_auth(ctx, True)
if 'InvalidRom' in args: if 'InvalidRom' in args:
raise Exception('Invalid ROM detected, please verify that you have loaded the correct rom and reconnect your snes') raise Exception(
'Invalid ROM detected, please verify that you have loaded the correct rom and reconnect your snes (/snes)')
if 'SlotAlreadyTaken' in args: if 'SlotAlreadyTaken' in args:
raise Exception('Player slot already in use for that team') raise Exception('Player slot already in use for that team')
if 'IncompatibleVersion' in args:
raise Exception('Server reported your client version as incompatible')
raise Exception('Connection refused by the multiworld host') raise Exception('Connection refused by the multiworld host')
if cmd == 'Connected': elif cmd == 'Connected':
ctx.team, ctx.slot = args[0] ctx.team, ctx.slot = args[0]
ctx.player_names = {p: n for p, n in args[1]} ctx.player_names = {p: n for p, n in args[1]}
msgs = [] msgs = []
@ -642,7 +653,7 @@ async def process_server_cmd(ctx : Context, cmd, args):
if msgs: if msgs:
await send_msgs(ctx.socket, msgs) await send_msgs(ctx.socket, msgs)
if cmd == 'ReceivedItems': elif cmd == 'ReceivedItems':
start_index, items = args start_index, items = args
if start_index == 0: if start_index == 0:
ctx.items_received = [] ctx.items_received = []
@ -656,36 +667,52 @@ async def process_server_cmd(ctx : Context, cmd, args):
ctx.items_received.append(ReceivedItem(*item)) ctx.items_received.append(ReceivedItem(*item))
ctx.watcher_event.set() ctx.watcher_event.set()
if cmd == 'LocationInfo': elif cmd == 'LocationInfo':
for location, item, player in args: for location, item, player in args:
if location not in ctx.locations_info: if location not in ctx.locations_info:
replacements = {0xA2: 'Small Key', 0x9D: 'Big Key', 0x8D: 'Compass', 0x7D: 'Map'} replacements = {0xA2: 'Small Key', 0x9D: 'Big Key', 0x8D: 'Compass', 0x7D: 'Map'}
item_name = replacements.get(item, get_item_name_from_id(item)) item_name = replacements.get(item, get_item_name_from_id(item))
logging.info(f"Saw {color(item_name, 'red', 'bold')} at {list(Regions.location_table.keys())[location - 1]}") logging.info(
f"Saw {color(item_name, 'red', 'bold')} at {list(Regions.location_table.keys())[location - 1]}")
ctx.locations_info[location] = (item, player) ctx.locations_info[location] = (item, player)
ctx.watcher_event.set() ctx.watcher_event.set()
if cmd == 'ItemSent': elif cmd == 'ItemSent':
player_sent, location, player_recvd, item = args player_sent, location, player_recvd, item = args
item = color(get_item_name_from_id(item), 'cyan' if player_sent != ctx.slot else 'green') item = color(get_item_name_from_id(item), 'cyan' if player_sent != ctx.slot else 'green')
player_sent = color(ctx.player_names[player_sent], 'yellow' if player_sent != ctx.slot else 'magenta') player_sent = color(ctx.player_names[player_sent], 'yellow' if player_sent != ctx.slot else 'magenta')
player_recvd = color(ctx.player_names[player_recvd], 'yellow' if player_recvd != ctx.slot else 'magenta') player_recvd = color(ctx.player_names[player_recvd], 'yellow' if player_recvd != ctx.slot else 'magenta')
logging.info('%s sent %s to %s (%s)' % (player_sent, item, player_recvd, get_location_name_from_address(location))) logging.info(
'%s sent %s to %s (%s)' % (player_sent, item, player_recvd, get_location_name_from_address(location)))
if cmd == 'Print': elif cmd == 'Hint':
hints = [Utils.Hint(*hint) for hint in args]
for hint in hints:
item = color(get_item_name_from_id(hint.item), 'green' if hint.found else 'cyan')
player_find = color(ctx.player_names[hint.finding_player],
'yellow' if hint.finding_player != ctx.slot else 'magenta')
player_recvd = color(ctx.player_names[hint.receiving_player],
'yellow' if hint.receiving_player != ctx.slot else 'magenta')
logging.info(f"[Hint]: {player_recvd}'s {item} can be found "
f"at {get_location_name_from_address(hint.location)} in {player_find}'s World." +
(" (found)" if hint.found else ""))
elif cmd == 'Print':
logging.info(args) logging.info(args)
async def server_auth(ctx : Context, password_requested):
async def server_auth(ctx: Context, password_requested):
if password_requested and not ctx.password: if password_requested and not ctx.password:
logging.info('Enter the password required to join this game:') logging.info('Enter the password required to join this game:')
ctx.password = await console_input(ctx) ctx.password = await console_input(ctx)
if ctx.rom is None: if ctx.rom is None:
ctx.awaiting_rom = True ctx.awaiting_rom = True
logging.info('No ROM detected, awaiting snes connection to authenticate to the multiworld server') logging.info('No ROM detected, awaiting snes connection to authenticate to the multiworld server (/snes)')
return return
ctx.awaiting_rom = False ctx.awaiting_rom = False
ctx.auth = ctx.rom.copy() ctx.auth = ctx.rom.copy()
await send_msgs(ctx.socket, [['Connect', {'password': ctx.password, 'rom': ctx.auth}]]) await send_msgs(ctx.socket, [['Connect', {
'password': ctx.password, 'rom': ctx.auth, 'version': [1, 0, 0], 'tags': ['Berserker']
}]])
async def console_input(ctx : Context): async def console_input(ctx : Context):
ctx.input_requests += 1 ctx.input_requests += 1
@ -714,38 +741,43 @@ async def console_loop(ctx : Context):
if not command: if not command:
continue continue
if command[0] == '/exit': if command[0][:1] != '/':
ctx.exit_event.set() asyncio.create_task(send_msgs(ctx.socket, [['Say', input]]))
continue
if command[0] == '/snes': precommand = command[0][1:]
if precommand == 'exit':
ctx.exit_event.set()
elif precommand == 'snes':
ctx.snes_reconnect_address = None ctx.snes_reconnect_address = None
asyncio.create_task(snes_connect(ctx, command[1] if len(command) > 1 else ctx.snes_address)) asyncio.create_task(snes_connect(ctx, command[1] if len(command) > 1 else ctx.snes_address))
if command[0] in ['/snes_close', '/snes_quit']: elif precommand in {'snes_close', 'snes_quit'}:
ctx.snes_reconnect_address = None ctx.snes_reconnect_address = None
if ctx.snes_socket is not None and not ctx.snes_socket.closed: if ctx.snes_socket is not None and not ctx.snes_socket.closed:
await ctx.snes_socket.close() await ctx.snes_socket.close()
if command[0] in ['/connect', '/reconnect']: elif precommand in {'connect', 'reconnect'}:
ctx.server_address = None ctx.server_address = None
asyncio.create_task(connect(ctx, command[1] if len(command) > 1 else None)) asyncio.create_task(connect(ctx, command[1] if len(command) > 1 else None))
if command[0] == '/disconnect': elif precommand == 'disconnect':
ctx.server_address = None ctx.server_address = None
asyncio.create_task(disconnect(ctx)) asyncio.create_task(disconnect(ctx))
if command[0][:1] != '/':
asyncio.create_task(send_msgs(ctx.socket, [['Say', input]]))
if command[0] == '/received':
elif precommand == 'received':
logging.info('Received items:') logging.info('Received items:')
for index, item in enumerate(ctx.items_received, 1): for index, item in enumerate(ctx.items_received, 1):
logging.info('%s from %s (%s) (%d/%d in list)' % ( logging.info('%s from %s (%s) (%d/%d in list)' % (
color(get_item_name_from_id(item.item), 'red', 'bold'), color(ctx.player_names[item.player], 'yellow'), color(get_item_name_from_id(item.item), 'red', 'bold'),
color(ctx.player_names[item.player], 'yellow'),
get_location_name_from_address(item.location), index, len(ctx.items_received))) get_location_name_from_address(item.location), index, len(ctx.items_received)))
if command[0] == '/missing': elif precommand == 'missing':
for location in [k for k, v in Regions.location_table.items() if type(v[0]) is int]: for location in [k for k, v in Regions.location_table.items() if type(v[0]) is int]:
if location not in ctx.locations_checked: if location not in ctx.locations_checked:
logging.info('Missing: ' + location) logging.info('Missing: ' + location)
if command[0] == '/getitem' and len(command) > 1: elif precommand == 'getitem' and len(command) > 1:
item = input[9:] item = input[9:]
item_id = Items.item_table[item][3] if item in Items.item_table else None item_id = Items.item_table[item][3] if item in Items.item_table else None
if type(item_id) is int and item_id in range(0x100): if type(item_id) is int and item_id in range(0x100):
@ -754,9 +786,10 @@ async def console_loop(ctx : Context):
snes_buffered_write(ctx, RECV_ITEM_PLAYER_ADDR, bytes([0])) snes_buffered_write(ctx, RECV_ITEM_PLAYER_ADDR, bytes([0]))
else: else:
logging.info('Invalid item: ' + item) logging.info('Invalid item: ' + item)
if command[0] == "/license": elif precommand == "license":
with open("LICENSE") as f: with open("LICENSE") as f:
logging.info(f.read()) logging.info(f.read())
await snes_flush_writes(ctx) await snes_flush_writes(ctx)
def get_item_name_from_id(code): def get_item_name_from_id(code):

View File

@ -8,6 +8,7 @@ import shlex
import urllib.request import urllib.request
import zlib import zlib
import collections import collections
import typing
import ModuleUpdate import ModuleUpdate
ModuleUpdate.update() ModuleUpdate.update()
@ -20,7 +21,11 @@ import Regions
import Utils import Utils
from MultiClient import ReceivedItem, get_item_name_from_id, get_location_name_from_address from MultiClient import ReceivedItem, get_item_name_from_id, get_location_name_from_address
class Client: class Client:
version: typing.List[int] = [0, 0, 0]
tags: typing.List[str] = []
def __init__(self, socket): def __init__(self, socket):
self.socket = socket self.socket = socket
self.auth = False self.auth = False
@ -28,9 +33,12 @@ class Client:
self.team = None self.team = None
self.slot = None self.slot = None
self.send_index = 0 self.send_index = 0
self.tags = []
self.version = [0, 0, 0]
class Context: class Context:
def __init__(self, host:str, port:int, password:str, location_check_points:int, hint_cost:int): def __init__(self, host: str, port: int, password: str, location_check_points: int, hint_cost: int):
self.data_filename = None self.data_filename = None
self.save_filename = None self.save_filename = None
self.disable_save = False self.disable_save = False
@ -69,6 +77,7 @@ class Context:
logging.info(f'Loaded save file with {sum([len(p) for p in received_items.values()])} received items ' logging.info(f'Loaded save file with {sum([len(p) for p in received_items.values()])} received items '
f'for {len(received_items)} players') f'for {len(received_items)} players')
async def send_msgs(websocket, msgs): async def send_msgs(websocket, msgs):
if not websocket or not websocket.open or websocket.closed: if not websocket or not websocket.open or websocket.closed:
return return
@ -91,17 +100,35 @@ def notify_all(ctx : Context, text):
logging.info("Notice (all): %s" % text) logging.info("Notice (all): %s" % text)
broadcast_all(ctx, [['Print', text]]) broadcast_all(ctx, [['Print', text]])
def notify_team(ctx : Context, team : int, text : str):
logging.info("Notice (Team #%d): %s" % (team+1, text)) def notify_team(ctx: Context, team: int, text: str):
logging.info("Notice (Team #%d): %s" % (team + 1, text))
broadcast_team(ctx, team, [['Print', text]]) broadcast_team(ctx, team, [['Print', text]])
def notify_client(client : Client, text : str):
def notify_client(client: Client, text: str):
if not client.auth: if not client.auth:
return return
logging.info("Notice (Player %s in team %d): %s" % (client.name, client.team+1, text)) logging.info("Notice (Player %s in team %d): %s" % (client.name, client.team + 1, text))
asyncio.create_task(send_msgs(client.socket, [['Print', text]])) asyncio.create_task(send_msgs(client.socket, [['Print', text]]))
async def server(websocket, path, ctx : Context):
# separated out, due to compatibilty between client's
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]
for cmd, text in texts:
logging.info("Notice (Team #%d): %s" % (team + 1, text))
for client in ctx.clients:
if client.auth and client.team == team:
if "Berserker" in client.tags:
payload = cmd
else:
payload = texts
asyncio.create_task(send_msgs(client.socket, payload))
async def server(websocket, path, ctx: Context):
client = Client(websocket) client = Client(websocket)
ctx.clients.append(client) ctx.clients.append(client)
@ -126,7 +153,9 @@ async def server(websocket, path, ctx : Context):
async def on_client_connected(ctx : Context, client : Client): async def on_client_connected(ctx : Context, client : Client):
await send_msgs(client.socket, [['RoomInfo', { await send_msgs(client.socket, [['RoomInfo', {
'password': ctx.password is not None, 'password': ctx.password is not None,
'players': [(client.team, client.slot, client.name) for client in ctx.clients if client.auth] 'players': [(client.team, client.slot, client.name) for client in ctx.clients if client.auth],
'tags': ['Berserker'],
'version': "1.0.0"
}]]) }]])
async def on_client_disconnected(ctx : Context, client : Client): async def on_client_disconnected(ctx : Context, client : Client):
@ -167,7 +196,8 @@ def get_connected_players_string(ctx : Context):
text += f'{c.name} ' text += f'{c.name} '
return 'Connected players: ' + text[:-1] return 'Connected players: ' + text[:-1]
def get_received_items(ctx : Context, team, player):
def get_received_items(ctx: Context, team: int, player: int):
return ctx.received_items.setdefault((team, player), []) return ctx.received_items.setdefault((team, player), [])
def tuplize_received_items(items): def tuplize_received_items(items):
@ -182,12 +212,14 @@ def send_new_items(ctx : Context):
asyncio.create_task(send_msgs(client.socket, [['ReceivedItems', (client.send_index, tuplize_received_items(items)[client.send_index:])]])) asyncio.create_task(send_msgs(client.socket, [['ReceivedItems', (client.send_index, tuplize_received_items(items)[client.send_index:])]]))
client.send_index = len(items) client.send_index = len(items)
def forfeit_player(ctx : Context, team, slot):
def forfeit_player(ctx: Context, team, slot):
all_locations = [values[0] for values in Regions.location_table.values() if type(values[0]) is int] all_locations = [values[0] for values in Regions.location_table.values() if type(values[0]) is int]
notify_all(ctx, "%s (Team #%d) has forfeited" % (ctx.player_names[(team, slot)], team + 1)) notify_all(ctx, "%s (Team #%d) has forfeited" % (ctx.player_names[(team, slot)], team + 1))
register_location_checks(ctx, team, slot, all_locations) register_location_checks(ctx, team, slot, all_locations)
def register_location_checks(ctx : Context, team, slot, locations):
def register_location_checks(ctx: Context, team, slot, locations):
ctx.location_checks[team, slot] |= set(locations) ctx.location_checks[team, slot] |= set(locations)
found_items = False found_items = False
@ -214,7 +246,8 @@ def register_location_checks(ctx : Context, team, slot, locations):
if found_items: if found_items:
save(ctx) save(ctx)
def save(ctx:Context):
def save(ctx: Context):
if not ctx.disable_save: if not ctx.disable_save:
try: try:
with open(ctx.save_filename, "wb") as f: with open(ctx.save_filename, "wb") as f:
@ -223,21 +256,29 @@ def save(ctx:Context):
except Exception as e: except Exception as e:
logging.exception(e) logging.exception(e)
def collect_hints(ctx:Context, team, slot, item:str) -> list:
def collect_hints(ctx: Context, team: int, slot: int, item: str) -> typing.List[Utils.Hint]:
hints = [] hints = []
seeked_item_id = Items.item_table[item][3] seeked_item_id = Items.lookup_lower_name_to_id[item]
for check, result in ctx.locations.items(): for check, result in ctx.locations.items():
item_id, receiving_player = result item_id, receiving_player = result
if receiving_player == slot and item_id == seeked_item_id: if receiving_player == slot and item_id == seeked_item_id:
location_id, finding_player = check location_id, finding_player = check
found = location_id in ctx.location_checks[team, finding_player] found = location_id in ctx.location_checks[team, finding_player]
hinttext = f"[Hint]: {ctx.player_names[(team, slot)]}'s {item} can be found at " \ hints.append(Utils.Hint(receiving_player, finding_player, location_id, item_id, found))
f"{get_location_name_from_address(location_id)} in {ctx.player_names[team, finding_player]}'s World."
hints.append((found, hinttext + (" (found)" if found else "")))
return hints return hints
async def process_client_cmd(ctx : Context, client : Client, cmd, args):
def format_hint(ctx: Context, team: int, hint: Utils.Hint) -> str:
return f"[Hint]: {ctx.player_names[team, hint.receiving_player]}'s " \
f"{Items.lookup_id_to_name[hint.item]} can be found " \
f"at {get_location_name_from_address(hint.location)} " \
f"in {ctx.player_names[team, hint.finding_player]}'s World." \
+ (" (found)" if hint.found else "")
async def process_client_cmd(ctx: Context, client: Client, cmd, args):
if type(cmd) is not str: if type(cmd) is not str:
await send_msgs(client.socket, [['InvalidCmd']]) await send_msgs(client.socket, [['InvalidCmd']])
return return
@ -268,7 +309,10 @@ async def process_client_cmd(ctx : Context, client : Client, cmd, args):
await send_msgs(client.socket, [['ConnectionRefused', list(errors)]]) await send_msgs(client.socket, [['ConnectionRefused', list(errors)]])
else: else:
client.auth = True client.auth = True
reply = [['Connected', [(client.team, client.slot), [(p, n) for (t, p), n in ctx.player_names.items() if t == client.team]]]] client.version = args.get('version', Client.version)
client.tags = args.get('tags', Client.tags)
reply = [['Connected', [(client.team, client.slot),
[(p, n) for (t, p), n in ctx.player_names.items() if t == client.team]]]]
items = get_received_items(ctx, client.team, client.slot) items = get_received_items(ctx, client.team, client.slot)
if items: if items:
reply.append(['ReceivedItems', (0, tuplize_received_items(items))]) reply.append(['ReceivedItems', (0, tuplize_received_items(items))])
@ -331,20 +375,20 @@ async def process_client_cmd(ctx : Context, client : Client, cmd, args):
timer = 10 timer = 10
asyncio.create_task(countdown(ctx, timer)) asyncio.create_task(countdown(ctx, timer))
elif args.startswith("!hint"): elif args.startswith("!hint"):
points_available = ctx.location_check_points * len(ctx.location_checks[client.team, client.slot]) - ctx.hint_cost*ctx.hints_used[client.team, client.slot] points_available = ctx.location_check_points * len(ctx.location_checks[client.team, client.slot]) - \
itemname = args[6:] ctx.hint_cost * ctx.hints_used[client.team, client.slot]
if not itemname: item_name = args[6:].lower()
notify_client(client, "Use !hint {itemname}, for example !hint Lamp. " if not item_name:
notify_client(client, "Use !hint {item_name}, for example !hint Lamp. "
f"A hint costs {ctx.hint_cost} points. " f"A hint costs {ctx.hint_cost} points. "
f"You have {points_available} points.") f"You have {points_available} points.")
elif itemname in Items.item_table: elif item_name in Items.lookup_lower_name_to_id:
hints = collect_hints(ctx, client.team, client.slot, itemname) hints = collect_hints(ctx, client.team, client.slot, item_name)
found = 0 found = 0
for already_found, hint in hints: for hint in hints:
found += 1 - already_found found += 1 - hint.found
if not found: if not found:
for already_found, hint in hints: notify_hints(ctx, client.team, format_hint(ctx, client.team, hints))
notify_team(ctx, client.team, hint)
notify_client(client, "No new items found, points refunded.") notify_client(client, "No new items found, points refunded.")
else: else:
if ctx.hint_cost: if ctx.hint_cost:
@ -354,15 +398,14 @@ async def process_client_cmd(ctx : Context, client : Client, cmd, args):
if can_pay: if can_pay:
ctx.hints_used[client.team, client.slot] += found ctx.hints_used[client.team, client.slot] += found
for already_found, hint in hints: notify_hints(ctx, client.team, format_hint(ctx, client.team, hints))
notify_team(ctx, client.team, hint)
save(ctx) save(ctx)
else: else:
notify_client(client, f"You can't afford the hint. " notify_client(client, f"You can't afford the hint. "
f"You have {points_available} points and need at least {ctx.hint_cost}, " f"You have {points_available} points and need at least {ctx.hint_cost}, "
f"more if multiple items are still be found.") f"more if multiple items are still to be found.")
else: else:
notify_client(client, f'Item "{itemname}" not found.') notify_client(client, f'Item "{item_name}" not found.')
def set_password(ctx : Context, password): def set_password(ctx : Context, password):
ctx.password = password ctx.password = password
@ -407,10 +450,11 @@ async def console(ctx : Context):
forfeit_player(ctx, team, slot) forfeit_player(ctx, team, slot)
if command[0] == '/senditem' and len(command) > 2: if command[0] == '/senditem' and len(command) > 2:
[(player, item)] = re.findall(r'\S* (\S*) (.*)', input) [(player, item)] = re.findall(r'\S* (\S*) (.*)', input)
if item in Items.item_table: item = item.lower()
if item in Items.lookup_lower_name_to_id:
for client in ctx.clients: for client in ctx.clients:
if client.auth and client.name.lower() == player.lower(): if client.name.lower() == player.lower():
new_item = ReceivedItem(Items.item_table[item][3], "cheat console", client.slot) new_item = ReceivedItem(Items.lookup_lower_name_to_name[item], "cheat console", client.slot)
get_received_items(ctx, client.team, client.slot).append(new_item) get_received_items(ctx, client.team, client.slot).append(new_item)
notify_all(ctx, 'Cheat console: sending "' + item + '" to ' + client.name) notify_all(ctx, 'Cheat console: sending "' + item + '" to ' + client.name)
send_new_items(ctx) send_new_items(ctx)
@ -421,11 +465,10 @@ async def console(ctx : Context):
if len(command) == 1: if len(command) == 1:
logging.info("Use /hint {Playername} {itemname}\nFor example /hint Berserker Lamp") logging.info("Use /hint {Playername} {itemname}\nFor example /hint Berserker Lamp")
elif name.lower() == command[1].lower(): elif name.lower() == command[1].lower():
item = " ".join(command[2:]) item = " ".join(command[2:]).lower()
if item in Items.item_table: if item in Items.lookup_lower_name_to_id:
hints = collect_hints(ctx, team, slot, item) hints = collect_hints(ctx, team, slot, item)
for already_found, hint in hints: notify_hints(ctx, team, hints)
notify_team(ctx, team, hint)
else: else:
logging.warning("Unknown item: " + item) logging.warning("Unknown item: " + item)
if command[0][0] != '/': if command[0][0] != '/':

View File

@ -2,15 +2,27 @@ import os
import re import re
import subprocess import subprocess
import sys import sys
import typing
import functools
from yaml import load
try:
from yaml import CLoader as Loader
except ImportError:
from yaml import Loader
def int16_as_bytes(value): def int16_as_bytes(value):
value = value & 0xFFFF value = value & 0xFFFF
return [value & 0xFF, (value >> 8) & 0xFF] return [value & 0xFF, (value >> 8) & 0xFF]
def int32_as_bytes(value): def int32_as_bytes(value):
value = value & 0xFFFFFFFF value = value & 0xFFFFFFFF
return [value & 0xFF, (value >> 8) & 0xFF, (value >> 16) & 0xFF, (value >> 24) & 0xFF] return [value & 0xFF, (value >> 8) & 0xFF, (value >> 16) & 0xFF, (value >> 24) & 0xFF]
def pc_to_snes(value): def pc_to_snes(value):
return ((value<<1) & 0x7F0000)|(value & 0x7FFF)|0x8000 return ((value<<1) & 0x7F0000)|(value & 0x7FFF)|0x8000
@ -122,16 +134,19 @@ def make_new_base2current(old_rom='Zelda no Densetsu - Kamigami no Triforce (Jap
if offset - 1 in out_data: if offset - 1 in out_data:
out_data[offset-1].extend(out_data.pop(offset)) out_data[offset-1].extend(out_data.pop(offset))
with open('data/base2current.json', 'wt') as outfile: with open('data/base2current.json', 'wt') as outfile:
json.dump([{key:value} for key, value in out_data.items()], outfile, separators=(",", ":")) json.dump([{key: value} for key, value in out_data.items()], outfile, separators=(",", ":"))
basemd5 = hashlib.md5() basemd5 = hashlib.md5()
basemd5.update(new_rom_data) basemd5.update(new_rom_data)
return "New Rom Hash: " + basemd5.hexdigest() return "New Rom Hash: " + basemd5.hexdigest()
from yaml import load
import functools
try: from yaml import CLoader as Loader
except ImportError: from yaml import Loader
parse_yaml = functools.partial(load, Loader=Loader) parse_yaml = functools.partial(load, Loader=Loader)
class Hint(typing.NamedTuple):
receiving_player: int
finding_player: int
location: int
item: int
found: bool