357 lines
11 KiB
Python
357 lines
11 KiB
Python
validKeyList = [
|
|
# top row
|
|
'escape', 'f1', 'f2', 'f3', 'f4', 'f5', 'f6', 'f7', 'f8', 'f9', 'f10', 'f11', 'f12',
|
|
# keyboard
|
|
'`', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '-', '=', 'backspace',
|
|
'tab', 'q', 'w', 'e', 'r', 't', 'y', 'u', 'i', 'o', 'p', '[', ']', '\\',
|
|
'capslock', 'a', 's', 'd', 'f', 'g', 'h', 'j', 'k', 'l', 'semicolon', '\'', 'enter',
|
|
'shift', 'z', 'x', 'c', 'v', 'b', 'n', 'm', ',', '.', '/', 'rshift',
|
|
'ctrl', 'lwin', 'alt', 'space', 'rwin', 'ralt', 'rctrl',
|
|
# mouse
|
|
'mouse1', 'mouse2', 'mouse3', 'mouse4', 'mouse5', 'mwheelup', 'mwheeldown',
|
|
# 8 of the 9 keys to the upper-right (PrtScn can't be bound)
|
|
'scrolllock', 'numlock',
|
|
'ins', 'home', 'pgup',
|
|
'del', 'end', 'pgdn',
|
|
# arrows
|
|
'uparrow', 'downarrow',
|
|
'leftarrow', 'rightarrow'
|
|
]
|
|
|
|
bindTypes = [
|
|
'impulse',
|
|
'hold',
|
|
'toggle',
|
|
'double',
|
|
'repeat'
|
|
]
|
|
|
|
class bind:
|
|
'''
|
|
Parent class for all bind types.
|
|
Verifies key, creates local variables
|
|
'''
|
|
|
|
def __init__(self, key, fields):
|
|
self.alias = False
|
|
self.key = key
|
|
self.fields = fields
|
|
self.errors = []
|
|
self.targetType = None
|
|
|
|
# redefined for each unique type, default just verifies key
|
|
# and some other universal fields like alias and finds targetType
|
|
self.verify()
|
|
|
|
if type(self) == bind or len(self.fields) == 0:
|
|
return
|
|
# verify function should remove all fields relavent to the bind.
|
|
# Any extras are errors
|
|
|
|
self.errors.append(f'extra fields in "{self.key}":')
|
|
if isinstance(self.fields, str):
|
|
# iterating over a str returns each character,
|
|
# making meaningless error messages
|
|
self.errors.append(f' "{self.fields}"')
|
|
else:
|
|
for field in self.fields:
|
|
self.errors.append(f' "{field}"')
|
|
|
|
def verify(self):
|
|
|
|
try:
|
|
typeName, self.key = self.key.split(' ', 1)
|
|
self.key = self.key.lower()
|
|
except ValueError:
|
|
# catastrophic error: no type
|
|
self.errors.append(f'could not find type in "{self.key}"')
|
|
return
|
|
|
|
try:
|
|
self.alias = self.fields.pop('alias')
|
|
if not isinstance(self.alias, bool):
|
|
self.errors.append(f'alias should be yes or no, not "{self.alias}"')
|
|
self.alias = False
|
|
except (KeyError, AttributeError, TypeError):
|
|
self.alias = False
|
|
|
|
if (not self.alias) and (self.key not in validKeyList):
|
|
self.errors.append(f'invalid key name: "{self.key}"')
|
|
|
|
types = {
|
|
'impulse': impulse,
|
|
'hold': hold,
|
|
'toggle': toggle,
|
|
'double': double,
|
|
'repeat': repeat
|
|
}
|
|
for loopName, typeClass in types.items():
|
|
if loopName == typeName:
|
|
self.targetType = typeClass
|
|
break
|
|
|
|
if self.targetType is None:
|
|
self.errors.append(f'could not find type in "{self.key}"')
|
|
|
|
def toTargetType(self):
|
|
if self.targetType is None:
|
|
# do nothing
|
|
bind = self
|
|
else:
|
|
# cast to targetType, extend errors
|
|
bind = self.targetType(self.key, self.fields)
|
|
bind.errors.extend(self.errors)
|
|
return bind
|
|
|
|
def err(self, message):
|
|
self.errors.append(f'{type(self).__name__} "{self.key}" {message}')
|
|
|
|
|
|
class impulse(bind):
|
|
def verify(self):
|
|
self.command = None
|
|
|
|
try:
|
|
self.command = self.fields.pop('command')
|
|
except (KeyError, AttributeError, TypeError):
|
|
self.fields = {'command': self.fields}
|
|
self.command = self.fields.pop('command')
|
|
|
|
if isinstance(self.command, str):
|
|
self.command = self.command.split(';')
|
|
|
|
elif not isinstance(self.command, list):
|
|
self.err('must be command or argument of string or list')
|
|
self.command = None
|
|
|
|
def toTF2(self) -> str:
|
|
if self.alias:
|
|
bindOrAlias = 'alias'
|
|
else:
|
|
bindOrAlias = 'bind'
|
|
|
|
allInstructions = self.shortcut(self.command)
|
|
instruction = ';'.join(allInstructions)
|
|
|
|
return f'{bindOrAlias} {self.key} "{instruction}"\n'
|
|
|
|
def shortcut(self, instList):
|
|
for i, instruction in enumerate(instList):
|
|
try:
|
|
cmd, restOfCmd = instruction.split(' ', 1)
|
|
except ValueError:
|
|
# no spaces in cmd
|
|
cmd, restOfCmd = instruction, ''
|
|
|
|
simpleSCs = {
|
|
"primary": "slot1",
|
|
"secondary": "slot2",
|
|
"melee": "slot3"
|
|
}
|
|
try:
|
|
cmd = simpleSCs[cmd]
|
|
except KeyError:
|
|
# not a shortcut
|
|
pass
|
|
|
|
if cmd == "voice":
|
|
cmd = "voicemenu"
|
|
restOfCmd = self.expandVoice(restOfCmd)
|
|
|
|
elif cmd == "build" or cmd == "destroy":
|
|
restOfCmd = self.expandBuildings(restOfCmd)
|
|
|
|
elif cmd == "load_itempreset" and restOfCmd.isalpha():
|
|
try:
|
|
restOfCmd = restOfCmd.lower()
|
|
restOfCmd = ['a','b','c','d'].index(restOfCmd)
|
|
except ValueError:
|
|
# not a load_itempreset shortcut
|
|
pass
|
|
|
|
if restOfCmd != "":
|
|
cmd += ' ' + restOfCmd
|
|
instList[i] = cmd
|
|
|
|
return instList
|
|
|
|
def expandVoice(self, keyword):
|
|
keyword = keyword.lower()
|
|
|
|
allLists = (
|
|
("medic", "thanks", "go", "move up", "go left", "go right", "yes", "no", "pass to me"),
|
|
("incoming", "spy", "sentry ahead", "teleporter here", "dispenser here", "sentry here", "activate uber", "uber ready"),
|
|
("help", "battle cry", "cheers", "jeers", "positive", "negative", "nice shot", "good job"),
|
|
)
|
|
|
|
for menu, voiceList in enumerate(allLists):
|
|
for selection, shortcut in enumerate(voiceList):
|
|
if keyword == shortcut:
|
|
return f'{menu} {selection}'
|
|
|
|
def expandBuildings(self, building):
|
|
buildingNums = {
|
|
"dispenser": "0 0",
|
|
"entrance": "1 0",
|
|
"exit": "1 1",
|
|
"sentry": "2 0"
|
|
}
|
|
for shortBuild, num in buildingNums.items():
|
|
if building == shortBuild:
|
|
return num
|
|
|
|
|
|
class hold(bind):
|
|
def verify(self):
|
|
self.press = None
|
|
self.release = None
|
|
if isinstance(self.fields, dict):
|
|
# verify press
|
|
try:
|
|
self.press = self.fields.pop('press')
|
|
if not isinstance(self.press, (str, list)):
|
|
self.err('press must be string or list')
|
|
self.press = None
|
|
except KeyError:
|
|
self.err('requires press field')
|
|
|
|
# verify release
|
|
try:
|
|
self.release = self.fields.pop('release')
|
|
if not isinstance(self.release, (str, list)):
|
|
self.err('release must be string or list')
|
|
self.release = None
|
|
except KeyError:
|
|
self.err('requires release field')
|
|
|
|
elif isinstance(self.fields, str):
|
|
self.fields = {'press': self.fields}
|
|
self.press = self.fields.pop('press')
|
|
else:
|
|
self.err('must be press and release, or argument of string')
|
|
|
|
|
|
class toggle(bind):
|
|
def verify(self):
|
|
self.on = None
|
|
self.off = None
|
|
if isinstance(self.fields, dict):
|
|
# verify on
|
|
try:
|
|
self.on = self.fields.pop('on')
|
|
if not isinstance(self.on, (str, list)):
|
|
self.err('on must be string or list')
|
|
self.on = None
|
|
except KeyError:
|
|
self.err('requires on field')
|
|
|
|
# verify off
|
|
try:
|
|
self.off = self.fields.pop('off')
|
|
if not isinstance(self.off, (str, list)):
|
|
self.err('off must be string or list')
|
|
self.off = None
|
|
except KeyError:
|
|
self.err('requires end field')
|
|
|
|
elif isinstance(self.fields, str):
|
|
self.fields = {'on': self.fields}
|
|
self.on = self.fields.pop('on')
|
|
else:
|
|
self.err('must be on and end, or argument of string')
|
|
|
|
|
|
class double(bind):
|
|
defaultDict = {}
|
|
condDict = {}
|
|
|
|
def verify(self):
|
|
self.primary = None
|
|
self.secondary = None
|
|
self.condition = None
|
|
|
|
# name of a bind type
|
|
self.type = None
|
|
|
|
# either "released" (default) or "both"
|
|
self.cancel = 'released'
|
|
|
|
# toggler
|
|
try:
|
|
self.condition = self.fields.pop('condition')
|
|
if self.condition not in validKeyList:
|
|
self.err(f'has invalid condition to toggle: "{self.condition}"')
|
|
except KeyError:
|
|
self.err('requires condition to toggle')
|
|
|
|
# cancel mode
|
|
try:
|
|
self.cancel = self.fields.pop('cancel')
|
|
if self.cancel not in ['released', 'both']:
|
|
self.err(f'cancel must be either "released" or "both", not "{self.cancel}"')
|
|
except KeyError:
|
|
# maintain default
|
|
pass
|
|
|
|
# type
|
|
try:
|
|
self.type = self.fields.pop('type')
|
|
if self.type not in bindTypes:
|
|
# catastrophic: invalid type
|
|
self.err(f'has invalid type: "{self.type}"')
|
|
return
|
|
except KeyError:
|
|
# catastrophic: no type given
|
|
self.err('requires type')
|
|
return
|
|
|
|
# primary action
|
|
try:
|
|
mainSection = self.fields.pop('primary')
|
|
# mainSection.update({'alias': True})
|
|
|
|
mainAction = bind(f'{self.type} primary', mainSection)
|
|
if mainAction.targetType is not None:
|
|
mainAction = mainAction.targetType(mainAction.key, mainAction.fields)
|
|
|
|
self.errors.extend(mainAction.errors)
|
|
except KeyError:
|
|
self.err('requires primary action')
|
|
|
|
# secondary action
|
|
try:
|
|
altSection = self.fields.pop('secondary')
|
|
# altSection.update({'alias': True})
|
|
|
|
altBind = bind(f'{self.type} secondary', altSection)
|
|
if altBind.targetType is not None:
|
|
altBind = altBind.targetType(altBind.key, altBind.fields)
|
|
|
|
self.errors.extend(altBind.errors)
|
|
except KeyError:
|
|
self.err('requires secondary action')
|
|
|
|
|
|
class repeat(bind):
|
|
def verify(self):
|
|
self.interval = None
|
|
self.command = None
|
|
|
|
try:
|
|
intervalStr = str(self.fields.pop('interval'))
|
|
self.interval = int(intervalStr)
|
|
if self.interval <= 0:
|
|
self.err('interval must be greater than 0')
|
|
except KeyError:
|
|
self.err('requires interval')
|
|
except ValueError:
|
|
self.err(f'has invalid number of ticks: "{self.interval}"')
|
|
|
|
try:
|
|
self.command = self.fields.pop('command')
|
|
if not isinstance(self.command, (str, list)):
|
|
self.err('command must be string or list')
|
|
self.command = None
|
|
except KeyError:
|
|
self.err('requires command')
|