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
|
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 = {}
|
||||||
|
|
139
Items.py
139
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 (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,8 +227,12 @@ 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),
|
||||||
|
@ -175,3 +240,7 @@ item_table = {'Bow': (True, False, None, 0x0B, 'You have\nchosen the\narcher cla
|
||||||
'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()}
|
||||||
|
|
|
@ -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
|
||||||
|
|
||||||
def color_code(*args):
|
|
||||||
codes = {'reset': 0, 'bold': 1, 'underline': 4, 'black': 30, 'red': 31, 'green': 32, 'yellow': 33, 'blue': 34,
|
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,
|
'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}
|
'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_code(*args):
|
||||||
|
return '\033[' + ';'.join([str(color_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):
|
||||||
|
|
101
MultiServer.py
101
MultiServer.py
|
@ -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,6 +33,9 @@ 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):
|
||||||
|
@ -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,16 +100,34 @@ 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):
|
def notify_team(ctx: Context, team: int, text: str):
|
||||||
logging.info("Notice (Team #%d): %s" % (team + 1, text))
|
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]]))
|
||||||
|
|
||||||
|
|
||||||
|
# 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):
|
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,11 +212,13 @@ 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)
|
||||||
|
|
||||||
|
@ -214,6 +246,7 @@ 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:
|
||||||
|
@ -223,20 +256,28 @@ 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
|
||||||
|
|
||||||
|
|
||||||
|
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):
|
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']])
|
||||||
|
@ -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] != '/':
|
||||||
|
|
25
Utils.py
25
Utils.py
|
@ -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
|
||||||
|
|
||||||
|
@ -128,10 +140,13 @@ def make_new_base2current(old_rom='Zelda no Densetsu - Kamigami no Triforce (Jap
|
||||||
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
|
||||||
|
|
Loading…
Reference in New Issue