implement server-client handshake and move hint system to optional colorama support
This commit is contained in:
parent
0986b36b39
commit
b04db006e0
|
@ -8,7 +8,6 @@ from EntranceShuffle import door_addresses
|
|||
from Utils import int16_as_bytes
|
||||
|
||||
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):
|
||||
self.players = players
|
||||
self.teams = 1
|
||||
|
@ -953,7 +952,7 @@ class Shop(object):
|
|||
|
||||
|
||||
class Spoiler(object):
|
||||
|
||||
world: World
|
||||
def __init__(self, world):
|
||||
self.world = world
|
||||
self.hashes = {}
|
||||
|
|
141
Items.py
141
Items.py
|
@ -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 (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 (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 (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'),
|
||||
'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'),
|
||||
'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'),
|
||||
'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'),
|
||||
'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'),
|
||||
'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'),
|
||||
'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'),
|
||||
'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'),
|
||||
'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'),
|
||||
'Green Pendant': (True, False, 'Crystal', [0x04, 0x38, 0x62, 0x00, 0x69, 0x01], None, None, None, None, None, None, None),
|
||||
'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'),
|
||||
'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 (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'),
|
||||
'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'),
|
||||
'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'),
|
||||
'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'),
|
||||
'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'),
|
||||
'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'),
|
||||
'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'),
|
||||
'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'),
|
||||
'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'),
|
||||
'Green Pendant': (
|
||||
True, False, 'Crystal', (0x04, 0x38, 0x62, 0x00, 0x69, 0x01), None, None, None, None, None, None, None),
|
||||
'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'),
|
||||
'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'),
|
||||
|
@ -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'),
|
||||
'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'),
|
||||
'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'),
|
||||
'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'),
|
||||
'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'),
|
||||
'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 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),
|
||||
'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),
|
||||
'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()}
|
||||
|
|
|
@ -17,6 +17,7 @@ import aioconsole
|
|||
|
||||
import Items
|
||||
import Regions
|
||||
import Utils
|
||||
|
||||
class ReceivedItem:
|
||||
def __init__(self, item, location, player):
|
||||
|
@ -59,15 +60,20 @@ class Context:
|
|||
self.rom = 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):
|
||||
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}
|
||||
return '\033[' + ';'.join([str(codes[arg]) for arg in args]) + 'm'
|
||||
return '\033[' + ';'.join([str(color_codes[arg]) for arg in args]) + 'm'
|
||||
|
||||
|
||||
def color(text, *args):
|
||||
return color_code(*args) + text + color_code('reset')
|
||||
|
||||
|
||||
RECONNECT_DELAY = 30
|
||||
|
||||
ROM_START = 0x000000
|
||||
|
@ -604,15 +610,17 @@ async def process_server_cmd(ctx : Context, cmd, args):
|
|||
logging.info('--------------------------------')
|
||||
logging.info('Room Information:')
|
||||
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']:
|
||||
logging.info('Password required')
|
||||
if len(args['players']) < 1:
|
||||
logging.info('No player connected')
|
||||
else:
|
||||
args['players'].sort()
|
||||
current_team = 0
|
||||
current_team = -1
|
||||
logging.info('Connected players:')
|
||||
logging.info(' Team #1')
|
||||
for team, slot, name in args['players']:
|
||||
if team != current_team:
|
||||
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))
|
||||
await server_auth(ctx, args['password'])
|
||||
|
||||
if cmd == 'ConnectionRefused':
|
||||
elif cmd == 'ConnectionRefused':
|
||||
if 'InvalidPassword' in args:
|
||||
logging.error('Invalid password')
|
||||
ctx.password = None
|
||||
await server_auth(ctx, True)
|
||||
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:
|
||||
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')
|
||||
|
||||
if cmd == 'Connected':
|
||||
elif cmd == 'Connected':
|
||||
ctx.team, ctx.slot = args[0]
|
||||
ctx.player_names = {p: n for p, n in args[1]}
|
||||
msgs = []
|
||||
|
@ -642,7 +653,7 @@ async def process_server_cmd(ctx : Context, cmd, args):
|
|||
if msgs:
|
||||
await send_msgs(ctx.socket, msgs)
|
||||
|
||||
if cmd == 'ReceivedItems':
|
||||
elif cmd == 'ReceivedItems':
|
||||
start_index, items = args
|
||||
if start_index == 0:
|
||||
ctx.items_received = []
|
||||
|
@ -656,36 +667,52 @@ async def process_server_cmd(ctx : Context, cmd, args):
|
|||
ctx.items_received.append(ReceivedItem(*item))
|
||||
ctx.watcher_event.set()
|
||||
|
||||
if cmd == 'LocationInfo':
|
||||
elif cmd == 'LocationInfo':
|
||||
for location, item, player in args:
|
||||
if location not in ctx.locations_info:
|
||||
replacements = {0xA2: 'Small Key', 0x9D: 'Big Key', 0x8D: 'Compass', 0x7D: 'Map'}
|
||||
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.watcher_event.set()
|
||||
|
||||
if cmd == 'ItemSent':
|
||||
elif cmd == 'ItemSent':
|
||||
player_sent, location, player_recvd, item = args
|
||||
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_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)
|
||||
|
||||
async def server_auth(ctx : Context, password_requested):
|
||||
|
||||
async def server_auth(ctx: Context, password_requested):
|
||||
if password_requested and not ctx.password:
|
||||
logging.info('Enter the password required to join this game:')
|
||||
ctx.password = await console_input(ctx)
|
||||
if ctx.rom is None:
|
||||
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
|
||||
ctx.awaiting_rom = False
|
||||
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):
|
||||
ctx.input_requests += 1
|
||||
|
@ -714,38 +741,43 @@ async def console_loop(ctx : Context):
|
|||
if not command:
|
||||
continue
|
||||
|
||||
if command[0] == '/exit':
|
||||
ctx.exit_event.set()
|
||||
if command[0][:1] != '/':
|
||||
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
|
||||
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
|
||||
if ctx.snes_socket is not None and not ctx.snes_socket.closed:
|
||||
await ctx.snes_socket.close()
|
||||
|
||||
if command[0] in ['/connect', '/reconnect']:
|
||||
elif precommand in {'connect', 'reconnect'}:
|
||||
ctx.server_address = 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
|
||||
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:')
|
||||
for index, item in enumerate(ctx.items_received, 1):
|
||||
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)))
|
||||
|
||||
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]:
|
||||
if location not in ctx.locations_checked:
|
||||
logging.info('Missing: ' + location)
|
||||
if command[0] == '/getitem' and len(command) > 1:
|
||||
elif precommand == 'getitem' and len(command) > 1:
|
||||
item = input[9:]
|
||||
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):
|
||||
|
@ -754,9 +786,10 @@ async def console_loop(ctx : Context):
|
|||
snes_buffered_write(ctx, RECV_ITEM_PLAYER_ADDR, bytes([0]))
|
||||
else:
|
||||
logging.info('Invalid item: ' + item)
|
||||
if command[0] == "/license":
|
||||
elif precommand == "license":
|
||||
with open("LICENSE") as f:
|
||||
logging.info(f.read())
|
||||
|
||||
await snes_flush_writes(ctx)
|
||||
|
||||
def get_item_name_from_id(code):
|
||||
|
|
125
MultiServer.py
125
MultiServer.py
|
@ -8,6 +8,7 @@ import shlex
|
|||
import urllib.request
|
||||
import zlib
|
||||
import collections
|
||||
import typing
|
||||
|
||||
import ModuleUpdate
|
||||
ModuleUpdate.update()
|
||||
|
@ -20,7 +21,11 @@ import Regions
|
|||
import Utils
|
||||
from MultiClient import ReceivedItem, get_item_name_from_id, get_location_name_from_address
|
||||
|
||||
|
||||
class Client:
|
||||
version: typing.List[int] = [0, 0, 0]
|
||||
tags: typing.List[str] = []
|
||||
|
||||
def __init__(self, socket):
|
||||
self.socket = socket
|
||||
self.auth = False
|
||||
|
@ -28,9 +33,12 @@ class Client:
|
|||
self.team = None
|
||||
self.slot = None
|
||||
self.send_index = 0
|
||||
self.tags = []
|
||||
self.version = [0, 0, 0]
|
||||
|
||||
|
||||
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.save_filename = None
|
||||
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 '
|
||||
f'for {len(received_items)} players')
|
||||
|
||||
|
||||
async def send_msgs(websocket, msgs):
|
||||
if not websocket or not websocket.open or websocket.closed:
|
||||
return
|
||||
|
@ -91,17 +100,35 @@ def notify_all(ctx : Context, text):
|
|||
logging.info("Notice (all): %s" % 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]])
|
||||
|
||||
def notify_client(client : Client, text : str):
|
||||
|
||||
def notify_client(client: Client, text: str):
|
||||
if not client.auth:
|
||||
return
|
||||
logging.info("Notice (Player %s in team %d): %s" % (client.name, client.team+1, text))
|
||||
asyncio.create_task(send_msgs(client.socket, [['Print', 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]]))
|
||||
|
||||
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)
|
||||
ctx.clients.append(client)
|
||||
|
||||
|
@ -126,7 +153,9 @@ async def server(websocket, path, ctx : Context):
|
|||
async def on_client_connected(ctx : Context, client : Client):
|
||||
await send_msgs(client.socket, [['RoomInfo', {
|
||||
'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):
|
||||
|
@ -167,7 +196,8 @@ def get_connected_players_string(ctx : Context):
|
|||
text += f'{c.name} '
|
||||
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), [])
|
||||
|
||||
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:])]]))
|
||||
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]
|
||||
notify_all(ctx, "%s (Team #%d) has forfeited" % (ctx.player_names[(team, slot)], team + 1))
|
||||
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)
|
||||
|
||||
found_items = False
|
||||
|
@ -214,7 +246,8 @@ def register_location_checks(ctx : Context, team, slot, locations):
|
|||
if found_items:
|
||||
save(ctx)
|
||||
|
||||
def save(ctx:Context):
|
||||
|
||||
def save(ctx: Context):
|
||||
if not ctx.disable_save:
|
||||
try:
|
||||
with open(ctx.save_filename, "wb") as f:
|
||||
|
@ -223,21 +256,29 @@ def save(ctx:Context):
|
|||
except Exception as 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 = []
|
||||
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():
|
||||
item_id, receiving_player = result
|
||||
if receiving_player == slot and item_id == seeked_item_id:
|
||||
location_id, finding_player = check
|
||||
found = location_id in ctx.location_checks[team, finding_player]
|
||||
hinttext = f"[Hint]: {ctx.player_names[(team, slot)]}'s {item} can be found at " \
|
||||
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 "")))
|
||||
hints.append(Utils.Hint(receiving_player, finding_player, location_id, item_id, found))
|
||||
|
||||
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:
|
||||
await send_msgs(client.socket, [['InvalidCmd']])
|
||||
return
|
||||
|
@ -268,7 +309,10 @@ async def process_client_cmd(ctx : Context, client : Client, cmd, args):
|
|||
await send_msgs(client.socket, [['ConnectionRefused', list(errors)]])
|
||||
else:
|
||||
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)
|
||||
if 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
|
||||
asyncio.create_task(countdown(ctx, timer))
|
||||
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]
|
||||
itemname = args[6:]
|
||||
if not itemname:
|
||||
notify_client(client, "Use !hint {itemname}, for example !hint Lamp. "
|
||||
points_available = ctx.location_check_points * len(ctx.location_checks[client.team, client.slot]) - \
|
||||
ctx.hint_cost * ctx.hints_used[client.team, client.slot]
|
||||
item_name = args[6:].lower()
|
||||
if not item_name:
|
||||
notify_client(client, "Use !hint {item_name}, for example !hint Lamp. "
|
||||
f"A hint costs {ctx.hint_cost} points. "
|
||||
f"You have {points_available} points.")
|
||||
elif itemname in Items.item_table:
|
||||
hints = collect_hints(ctx, client.team, client.slot, itemname)
|
||||
elif item_name in Items.lookup_lower_name_to_id:
|
||||
hints = collect_hints(ctx, client.team, client.slot, item_name)
|
||||
found = 0
|
||||
for already_found, hint in hints:
|
||||
found += 1 - already_found
|
||||
for hint in hints:
|
||||
found += 1 - hint.found
|
||||
if not found:
|
||||
for already_found, hint in hints:
|
||||
notify_team(ctx, client.team, hint)
|
||||
notify_hints(ctx, client.team, format_hint(ctx, client.team, hints))
|
||||
notify_client(client, "No new items found, points refunded.")
|
||||
else:
|
||||
if ctx.hint_cost:
|
||||
|
@ -354,15 +398,14 @@ async def process_client_cmd(ctx : Context, client : Client, cmd, args):
|
|||
|
||||
if can_pay:
|
||||
ctx.hints_used[client.team, client.slot] += found
|
||||
for already_found, hint in hints:
|
||||
notify_team(ctx, client.team, hint)
|
||||
notify_hints(ctx, client.team, format_hint(ctx, client.team, hints))
|
||||
save(ctx)
|
||||
else:
|
||||
notify_client(client, f"You can't afford the hint. "
|
||||
f"You have {points_available} points and need at least {ctx.hint_cost}, "
|
||||
f"more if multiple items are still be found.")
|
||||
f"You have {points_available} points and need at least {ctx.hint_cost}, "
|
||||
f"more if multiple items are still to be found.")
|
||||
else:
|
||||
notify_client(client, f'Item "{itemname}" not found.')
|
||||
notify_client(client, f'Item "{item_name}" not found.')
|
||||
|
||||
def set_password(ctx : Context, password):
|
||||
ctx.password = password
|
||||
|
@ -407,10 +450,11 @@ async def console(ctx : Context):
|
|||
forfeit_player(ctx, team, slot)
|
||||
if command[0] == '/senditem' and len(command) > 2:
|
||||
[(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:
|
||||
if client.auth and client.name.lower() == player.lower():
|
||||
new_item = ReceivedItem(Items.item_table[item][3], "cheat console", client.slot)
|
||||
if client.name.lower() == player.lower():
|
||||
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)
|
||||
notify_all(ctx, 'Cheat console: sending "' + item + '" to ' + client.name)
|
||||
send_new_items(ctx)
|
||||
|
@ -421,11 +465,10 @@ async def console(ctx : Context):
|
|||
if len(command) == 1:
|
||||
logging.info("Use /hint {Playername} {itemname}\nFor example /hint Berserker Lamp")
|
||||
elif name.lower() == command[1].lower():
|
||||
item = " ".join(command[2:])
|
||||
if item in Items.item_table:
|
||||
item = " ".join(command[2:]).lower()
|
||||
if item in Items.lookup_lower_name_to_id:
|
||||
hints = collect_hints(ctx, team, slot, item)
|
||||
for already_found, hint in hints:
|
||||
notify_team(ctx, team, hint)
|
||||
notify_hints(ctx, team, hints)
|
||||
else:
|
||||
logging.warning("Unknown item: " + item)
|
||||
if command[0][0] != '/':
|
||||
|
|
27
Utils.py
27
Utils.py
|
@ -2,15 +2,27 @@ import os
|
|||
import re
|
||||
import subprocess
|
||||
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):
|
||||
value = value & 0xFFFF
|
||||
return [value & 0xFF, (value >> 8) & 0xFF]
|
||||
|
||||
|
||||
def int32_as_bytes(value):
|
||||
value = value & 0xFFFFFFFF
|
||||
return [value & 0xFF, (value >> 8) & 0xFF, (value >> 16) & 0xFF, (value >> 24) & 0xFF]
|
||||
|
||||
|
||||
def pc_to_snes(value):
|
||||
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:
|
||||
out_data[offset-1].extend(out_data.pop(offset))
|
||||
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.update(new_rom_data)
|
||||
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)
|
||||
|
||||
|
||||
class Hint(typing.NamedTuple):
|
||||
receiving_player: int
|
||||
finding_player: int
|
||||
location: int
|
||||
item: int
|
||||
found: bool
|
||||
|
|
Loading…
Reference in New Issue