tfscript/src/tfscript/tftypes.py

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')