Added simple scripting for yaml files.
This commit is contained in:
parent
8355dccb33
commit
b332d579e9
6 changed files with 506 additions and 4 deletions
484
gamebase.py
484
gamebase.py
|
@ -25,6 +25,7 @@ class GameBase(object):
|
|||
self.__behaviors = {}
|
||||
self.__gameEvents = {}
|
||||
self.__IOCalls = {} # {str : function}
|
||||
self.__scripts = {} # functions with the same signature, but not callable by the user
|
||||
self.customValues = {} # for setting flags and such
|
||||
self.level = None
|
||||
self.persist = {} # {level : {thingName : thing}}
|
||||
|
@ -61,6 +62,11 @@ class GameBase(object):
|
|||
self.registerUseFunc('info', self.info)
|
||||
self.registerUseFunc('wear', self.wear)
|
||||
self.registerBehavior('wander', self.wander)
|
||||
self.registerScript('if', self.ifScript)
|
||||
self.registerScript('printf', self.printfScript)
|
||||
self.registerScript('set', self.setCustomValue)
|
||||
self.registerScript('spawn', self.spawnThing)
|
||||
self.registerScript('give', self.giveToPlayer)
|
||||
|
||||
# Helper functions
|
||||
|
||||
|
@ -175,6 +181,60 @@ Returns (thing, x, y). "Thing" can be None."""
|
|||
|
||||
return '\n'.join(ret)
|
||||
|
||||
def getValueFromString(self, arg: str):
|
||||
val = None
|
||||
validIdent = _re.match(r'[_A-Za-z][_0-9A-Za-z]*', arg)
|
||||
if arg[0] in '"\'': # assume it's a string
|
||||
val = arg[1:-1]
|
||||
elif _re.match(r'[+-]?(?:[0-9]*[.])?[0-9]+', arg) != None:
|
||||
if '.' in arg:
|
||||
val = float(arg)
|
||||
else:
|
||||
val = int(arg)
|
||||
elif arg.casefold() == 'true':
|
||||
val = True
|
||||
elif arg.casefold() == 'false':
|
||||
val = False
|
||||
elif arg.casefold() == 'playerx':
|
||||
val = self.playerx
|
||||
elif arg.casefold() == 'playery':
|
||||
val = self.playery
|
||||
elif arg.casefold() == 'playername':
|
||||
val = self.playerName
|
||||
elif arg.casefold() == 'playerinv':
|
||||
val = self.playerInv
|
||||
elif validIdent != None:
|
||||
if 'scriptLocal' in self.customValues and validIdent.group() in self.customValues['scriptLocal']:
|
||||
val = self.customValues['scriptLocal'][arg]
|
||||
elif validIdent.group() in self.customValues:
|
||||
val = self.customValues[arg]
|
||||
else:
|
||||
return False # for if statements; if a variable doesn't exist, should evaluate to False
|
||||
# evaluate all values of all indecies
|
||||
if validIdent.end() < len(arg):
|
||||
if arg[validIdent.end()] == '[':
|
||||
openBracket = validIdent.end()
|
||||
ptr = openBracket
|
||||
depth = 0
|
||||
while ptr < len(arg):
|
||||
if ptr == '[':
|
||||
depth += 1
|
||||
elif ptr == ']':
|
||||
depth -= 1
|
||||
if depth == 0:
|
||||
var = var[self.getValueFromString(arg[openBracket+1:ptr])]
|
||||
openBracket = ptr + 1
|
||||
ptr += 1
|
||||
if depth == 0 and arg[ptr] != '[':
|
||||
raise GameError('Invalid value syntax: {}'.format(arg))
|
||||
else:
|
||||
raise GameError('Invalid value syntax: {}'.format(arg))
|
||||
else:
|
||||
raise GameError('Invalid argument to getValueFromString: {}'.format(arg))
|
||||
return val
|
||||
|
||||
# commands
|
||||
|
||||
def go(self, args):
|
||||
"""go [-r] [to [the]] destination [additional arguments...]
|
||||
Go to a location. "walk" and "move" are aliases of go.
|
||||
|
@ -486,6 +546,7 @@ Object can be the name of the object, or its coordinates."""
|
|||
self.prevx, self.prevy = self.playerx, self.playery
|
||||
if self.onLevelLoad != None:
|
||||
self.onLevelLoad()
|
||||
self.parseScript(self.level.enterScript)
|
||||
|
||||
def saveGame(self, args):
|
||||
if len(args) < 1:
|
||||
|
@ -541,13 +602,86 @@ Object can be the name of the object, or its coordinates."""
|
|||
#_hq.heappush(self.eventQueue, (self.gameTime, _ge.NoOpEvent()))
|
||||
return
|
||||
|
||||
def dialog(self, dialogName, conversant):
|
||||
def dialog(self, dialogName, conversant = None):
|
||||
yaml = _yaml.YAML()
|
||||
dialog = None
|
||||
with open(dialogName, 'r') as f:
|
||||
dialog = yaml.load(f)
|
||||
self.getIO('dialog')(dialog)
|
||||
|
||||
def parseScript(self, instr: str):
|
||||
"""parses then runs a script."""
|
||||
#print('parseScript called with {}.'.format(instr))
|
||||
if instr == '':
|
||||
return # nothing to be done.
|
||||
literalStr = list(instr)
|
||||
inQuotes = False
|
||||
script = []
|
||||
stack = []
|
||||
ret = []
|
||||
argStart = 0
|
||||
c = 0
|
||||
l = 0
|
||||
while c < len(instr):
|
||||
if inQuotes:
|
||||
if instr[c] == '"':
|
||||
if instr[c-1] == '\\':
|
||||
del literalStr[l-1]
|
||||
l -= 1
|
||||
else:
|
||||
inQuotes = False
|
||||
del literalStr[l]
|
||||
l -= 1
|
||||
else:
|
||||
if instr[c] == '"': # quoted string
|
||||
if l > 0 and instr[c-1] == '\\':
|
||||
del literalStr[l-1]
|
||||
l -= 1
|
||||
else:
|
||||
inQuotes = True
|
||||
del literalStr[l]
|
||||
l -= 1
|
||||
elif instr[c] in ' \t\n;}{':
|
||||
if argStart != l:
|
||||
ret.append(''.join(literalStr[argStart:l]))
|
||||
if instr[c] == ';':
|
||||
script.append(ret)
|
||||
ret = []
|
||||
elif instr[c] == '{': # block
|
||||
stack.append(script)
|
||||
script.append(ret)
|
||||
script = []
|
||||
ret.append(script)
|
||||
ret = []
|
||||
del literalStr[l]
|
||||
l -= 1
|
||||
elif instr[c] == '}': # close block
|
||||
if len(ret) > 0:
|
||||
script.append(ret)
|
||||
script = stack.pop()
|
||||
ret = []
|
||||
del literalStr[l]
|
||||
l -= 1
|
||||
argStart = l + 1
|
||||
c += 1
|
||||
l += 1
|
||||
if argStart != l:
|
||||
ret.append(''.join(literalStr[argStart:l]))
|
||||
script.append(ret)
|
||||
#print('After parsing: {}'.format(script))
|
||||
self.customValues['scriptLocal'] = {}
|
||||
self.runScript(script)
|
||||
del self.customValues['scriptLocal']
|
||||
|
||||
def runScript(self, script: list):
|
||||
"""run a script"""
|
||||
for line in script:
|
||||
if line[0].casefold() == 'playercmd':
|
||||
# run a player command
|
||||
self.getIO('playercmd')(line[1:])
|
||||
else:
|
||||
self.__scripts[line[0]](line[1:])
|
||||
|
||||
def gameEventLoop(self):
|
||||
#print(self.skipLoop)
|
||||
if self.skipLoop:
|
||||
|
@ -727,6 +861,350 @@ Object can be the name of the object, or its coordinates."""
|
|||
print("The key doesn't fit that lock.", file = self.outstream)
|
||||
return 0.125
|
||||
|
||||
# default scripts
|
||||
|
||||
def ifScript(self, args):
|
||||
"""If statement: if [not] value [op value] script"""
|
||||
if len(args) < 2:
|
||||
raise GameError('Incomplete If statement: if {}'.format(' '.join(args)))
|
||||
inverse = False
|
||||
ret = False
|
||||
if args[0] == 'not':
|
||||
inverse = True
|
||||
args.pop(0)
|
||||
if len(args) < 2:
|
||||
raise GameError('Incomplete If statement: if {}'.format(' '.join(args)))
|
||||
|
||||
# evaluate condition
|
||||
val = self.parseValue(args[0])
|
||||
if args[1] in ('==', '!=', '<=', '>=', '<', '>'):
|
||||
if len(args) < 4:
|
||||
raise GameError('Incomplete If statement: if {}'.format(' '.join(args)))
|
||||
val2 = self.parseValue(args[2])
|
||||
if args[1] == '==':
|
||||
ret = val == val2
|
||||
elif args[1] == '!=':
|
||||
ret = val != val2
|
||||
elif args[1] == '<=':
|
||||
ret = val <= val2
|
||||
elif args[1] == '>=':
|
||||
ret = val >= val2
|
||||
elif args[1] == '<':
|
||||
ret = val < val2
|
||||
elif args[1] == '>':
|
||||
ret = val > val2
|
||||
args = args[3:]
|
||||
else:
|
||||
ret = bool(val)
|
||||
args = args[1:]
|
||||
if inverse:
|
||||
ret = not ret
|
||||
|
||||
# if condition is true, evaluate further
|
||||
if ret:
|
||||
if isinstance(args[-1], list):
|
||||
self.runScript(args[-1])
|
||||
else:
|
||||
self.runScript([args])
|
||||
|
||||
def printfScript(self, args):
|
||||
"""Assume the first argument is a stringable object, and format all others."""
|
||||
print(args[0].format(*[self.getValueFromString(i) for i in args[1:]]), file = self.outstream)
|
||||
|
||||
def setCustomValue(self, args):
|
||||
"""takes [customValue, op, value]"""
|
||||
env = self.customValues
|
||||
if len(args) > 0 and args[0] == 'local':
|
||||
if 'scriptLocal' in self.customValues:
|
||||
env = self.customValues['scriptLocal']
|
||||
else:
|
||||
raise GameError('Attempted to set a local variable without local scope.')
|
||||
args.pop(0)
|
||||
if len(args) < 3 or args[1] not in ('=', '+=', '-=', '*=', '/=', '%=', '//=', '**=', 'b=', '!=', '|=', '&=', '^='):
|
||||
raise GameError('Arguments are not fit for the setCustomValue script.')
|
||||
val = self.getValueFromString(args[2])
|
||||
|
||||
# set the value to a default value (0, 0.0, '', etc.) if not yet assigned
|
||||
if args[0] not in env and args[1] != '=':
|
||||
if isinstance(val, int):
|
||||
env[args[0]] = 0
|
||||
elif isinstance(val, float):
|
||||
env[args[0]] = 0.0
|
||||
elif isinstance(val, str):
|
||||
env[args[0]] = ''
|
||||
elif isinstance(val, bool):
|
||||
env[args[0]] = False
|
||||
# Beyond this point are types that can only be found from other customValues
|
||||
elif isinstance(val, tuple):
|
||||
env[args[0]] = ()
|
||||
elif isinstance(val, list):
|
||||
env[args[0]] = []
|
||||
elif isinstance(val, dict):
|
||||
env[args[0]] = {}
|
||||
else:
|
||||
raise GameError("Operation not supported for unassigned custom values of type {}: {}"
|
||||
.format(type(val), args[1]))
|
||||
|
||||
# done parsing, evaluate
|
||||
if args[1] == '=':
|
||||
env[args[0]] = val
|
||||
elif args[1] == '+=':
|
||||
env[args[0]] += val
|
||||
elif args[1] == '-=':
|
||||
env[args[0]] -= val
|
||||
elif args[1] == '*=':
|
||||
env[args[0]] *= val
|
||||
elif args[1] == '/=':
|
||||
env[args[0]] /= val
|
||||
elif args[1] == '%=':
|
||||
env[args[0]] %= val
|
||||
elif args[1] == '//=':
|
||||
env[args[0]] //= val
|
||||
elif args[1] == '**=':
|
||||
env[args[0]] **= val
|
||||
elif args[1] == 'b=':
|
||||
env[args[0]] = bool(val)
|
||||
elif args[1] == '!=':
|
||||
env[args[0]] = not bool(val)
|
||||
elif args[1] == '|=':
|
||||
env[args[0]] |= val
|
||||
elif args[1] == '&=':
|
||||
env[args[0]] &= val
|
||||
elif args[1] == '^=':
|
||||
env[args[0]] ^= val
|
||||
|
||||
def spawnThing(self, args):
|
||||
"""Spawns a thing. [type name=... location=... etc]"""
|
||||
if args[0].casefold() not in ('item', 'useable', 'npc', 'door', 'mapexit', 'mapentrance'):
|
||||
raise GameError("{} not a spawnable thing type.".format(args[0]))
|
||||
|
||||
# Get the standard fields that most Things have in common: name, coordinates, graphic.
|
||||
name = None
|
||||
x, y = -1, -1
|
||||
fgc, bgc, shape = None, None, None
|
||||
persist = False
|
||||
for i in args[1:]:
|
||||
if i[0:5].casefold() == 'name=':
|
||||
name = i[5:]
|
||||
elif i[0:10].casefold() == 'location=':
|
||||
_, x, y = self.parseCoords(i[10:].split())
|
||||
elif i[0:4].casefold() == 'fgc=':
|
||||
if len(i[4:]) != 7 or i[0] != '#':
|
||||
raise GameError("Invalid color: {}".format(i[4:]))
|
||||
for j in i[4:]:
|
||||
if j not in '0123456789abcdefABCDEF':
|
||||
raise GameError("Invalid color: {}".format(i[4:]))
|
||||
fgc = i[4:]
|
||||
elif i[0:4].casefold() == 'bgc=':
|
||||
if len(i[4:]) != 7 or i[0] != '#':
|
||||
raise GameError("Invalid color: {}".format(i[4:]))
|
||||
for j in i[4:]:
|
||||
if j not in '0123456789abcdefABCDEF':
|
||||
raise GameError("Invalid color: {}".format(i[4:]))
|
||||
bgc = i[4:]
|
||||
elif i[0:6].casefold() == 'shape=':
|
||||
if len(i[6:]) != 7 or i[0] != '#':
|
||||
raise GameError("Invalid color: {}".format(i[6:]))
|
||||
for j in i[6:]:
|
||||
if j not in '0123456789abcdefABCDEF':
|
||||
raise GameError("Invalid color: {}".format(i[6:]))
|
||||
shape = i[6:]
|
||||
elif i == 'persist':
|
||||
persist = True
|
||||
if x < 0 or y < 0:
|
||||
raise GameError("Cannot spawn thing: bad location")
|
||||
|
||||
# Unfortunately, there needs to be a case for each type.
|
||||
if args[0].casefold() == 'item':
|
||||
# spawn an item
|
||||
description = 'A nondescript item.'
|
||||
useFunc = ''
|
||||
useOnFunc = ''
|
||||
customValues = {}
|
||||
ranged = False
|
||||
if name == None:
|
||||
name = 'item {}'.format(self.nextThing)
|
||||
if fgc == None:
|
||||
fgc = _gm.Item.defaultGraphic['fgc']
|
||||
if bgc == None:
|
||||
bgc = _gm.Item.defaultGraphic['bgc']
|
||||
if shape == None:
|
||||
shape = _gm.Item.defaultGraphic['shape']
|
||||
for i in args[1:]:
|
||||
if i[0:12].casefold() == 'description=':
|
||||
description = i[12:]
|
||||
elif i[0:8].casefold() == 'usefunc=':
|
||||
useFunc = i[8:]
|
||||
elif i[0:10].casefold() == 'useonfunc=':
|
||||
useOnFunc = i[10:]
|
||||
elif i.casefold() == 'ranged':
|
||||
ranged = True
|
||||
elif i[0:3].casefold() == 'cv:':
|
||||
equalsign = i.index('=')
|
||||
cv = i[3:equalsign]
|
||||
customValues[cv] = getValueFromString(i[equalsign+1:])
|
||||
thing = _gm.Item(name, x, y, description, useFunc, useOnFunc, customValues, ranged, (bgc, fgc, shape))
|
||||
self.nextThing = self.level.addThing(thing, self.nextThing, persist)
|
||||
elif args[0].casefold() == 'useable':
|
||||
# spawn a useable thing
|
||||
description = 'A nondescript useable thing.'
|
||||
useFunc = ''
|
||||
customValues = {}
|
||||
playerx, playery = x, y
|
||||
if name == None:
|
||||
name = 'useable {}'.format(self.nextThing)
|
||||
if fgc == None:
|
||||
fgc = _gm.Item.defaultGraphic['fgc']
|
||||
if bgc == None:
|
||||
bgc = _gm.Item.defaultGraphic['bgc']
|
||||
if shape == None:
|
||||
shape = _gm.Item.defaultGraphic['shape']
|
||||
for i in args[1:]:
|
||||
if i[0:12].casefold() == 'description=':
|
||||
description = i[12:]
|
||||
elif i[0:8].casefold() == 'usefunc=':
|
||||
useFunc = i[8:]
|
||||
elif i[0:10].casefold() == 'playerpos=':
|
||||
_, playerx, playery = self.parseCoords(i[10:].split())
|
||||
elif i[0:3].casefold() == 'cv:':
|
||||
equalsign = i.index('=')
|
||||
cv = i[3:equalsign]
|
||||
customValues[cv] = getValueFromString(i[equalsign+1:])
|
||||
thing = _gm.Useable(name, x, y, description, useFunc, customValues, playerx, playery, (bgc, fgc, shape))
|
||||
self.nextThing = self.level.addThing(thing, self.nextThing, persist)
|
||||
elif args[0].casefold() == 'npc':
|
||||
# spawn an NPC
|
||||
description = 'A nondescript character.'
|
||||
behavior = ''
|
||||
inv = []
|
||||
customValues = {}
|
||||
playerx, playery = x, y
|
||||
if name == None:
|
||||
name = 'character {}'.format(self.nextThing)
|
||||
if fgc == None:
|
||||
fgc = _gm.Item.defaultGraphic['fgc']
|
||||
if bgc == None:
|
||||
bgc = _gm.Item.defaultGraphic['bgc']
|
||||
if shape == None:
|
||||
shape = _gm.Item.defaultGraphic['shape']
|
||||
for i in args[1:]:
|
||||
if i[0:12].casefold() == 'description=':
|
||||
description = i[12:]
|
||||
elif i[0:9].casefold() == 'behavior=':
|
||||
useFunc = i[9:]
|
||||
elif i[0:10].casefold() == 'playerpos=':
|
||||
_, playerx, playery = self.parseCoords(i[10:].split())
|
||||
elif i[0:3].casefold() == 'cv:':
|
||||
equalsign = i.index('=')
|
||||
cv = i[3:equalsign]
|
||||
customValues[cv] = getValueFromString(i[equalsign+1:])
|
||||
thing = _gm.NPC(name, x, y, description, behavior, inv, customValues, playerx, playery, False, (bgc, fgc, shape))
|
||||
self.nextThing = self.level.addThing(thing, self.nextThing, persist)
|
||||
elif args[0].casefold() == 'door':
|
||||
# spawn a door
|
||||
description = 'a nondescript door.'
|
||||
locked = False
|
||||
key = None
|
||||
if name == None:
|
||||
name = 'door {}'.format(self.nextThing)
|
||||
if fgc == None:
|
||||
fgc = _gm.Item.defaultGraphic['fgc']
|
||||
if bgc == None:
|
||||
bgc = _gm.Item.defaultGraphic['bgc']
|
||||
if shape == None:
|
||||
shape = _gm.Item.defaultGraphic['shape']
|
||||
for i in args[1:]:
|
||||
if i[0:12].casefold() == 'description=':
|
||||
description = i[12:]
|
||||
elif i.casefold() == 'locked':
|
||||
locked = True
|
||||
thing = _gm.Door(name, x, y, locked, description, key, (bgc, fgc, shape))
|
||||
self.nextThing = self.level.addThing(thing, self.nextThing, persist)
|
||||
elif args[0].casefold() == 'mapexit':
|
||||
# spawn an exit to another map (use with EXTREME caution!)
|
||||
destination = ''
|
||||
prefix = None
|
||||
exitid = 0
|
||||
if name == None:
|
||||
name = 'somewhere'
|
||||
if fgc == None:
|
||||
fgc = _gm.Item.defaultGraphic['fgc']
|
||||
if bgc == None:
|
||||
bgc = _gm.Item.defaultGraphic['bgc']
|
||||
if shape == None:
|
||||
shape = _gm.Item.defaultGraphic['shape']
|
||||
for i in args[1:]:
|
||||
if i[0:12].casefold() == 'destination=':
|
||||
destination = i[12:]
|
||||
elif i[0:7].casefold() == 'prefix=':
|
||||
prefix = i[7:]
|
||||
elif i[0:7].casefold() == 'exitid=':
|
||||
exitid = int(i[7:])
|
||||
thing = _gm.MapExit(name, x, y, exitid, destination, prefix, (bgc, fgc, shape))
|
||||
self.nextThing = self.level.addThing(thing, self.nextThing, persist)
|
||||
elif args[0].casefold() == 'mapentrance':
|
||||
# spawn a map entrance
|
||||
exitid = 0
|
||||
for i in args[1:]:
|
||||
if i[0:7].casefold() == 'exitid=':
|
||||
exitid = int(i[7:])
|
||||
thing = _gm.MapEntrance(x, y, exitid, name)
|
||||
self.nextThing = self.level.addThing(thing, self.nextThing, persist)
|
||||
|
||||
def giveToPlayer(self, args):
|
||||
"""We get to assume it's an item."""
|
||||
name = 'item {}'.format(self.nextThing)
|
||||
x, y = self.playerx, self.playery
|
||||
fgc, bgc, shape = _gm.Item.defaultGraphic['fgc'], _gm.Item.defaultGraphic['bgc'], _gm.Item.defaultGraphic['shape']
|
||||
description = 'A nondescript item.'
|
||||
persist = True
|
||||
useFunc = ''
|
||||
useOnFunc = ''
|
||||
customValues = {}
|
||||
ranged = False
|
||||
for i in args[1:]:
|
||||
if i[0:5].casefold() == 'name=':
|
||||
name = i[5:]
|
||||
elif i[0:10].casefold() == 'location=':
|
||||
_, x, y = self.parseCoords(i[10:].split())
|
||||
elif i[0:4].casefold() == 'fgc=':
|
||||
if len(i[4:]) != 7 or i[0] != '#':
|
||||
raise GameError("Invalid color: {}".format(i[4:]))
|
||||
for j in i[4:]:
|
||||
if j not in '0123456789abcdefABCDEF':
|
||||
raise GameError("Invalid color: {}".format(i[4:]))
|
||||
fgc = i[4:]
|
||||
elif i[0:4].casefold() == 'bgc=':
|
||||
if len(i[4:]) != 7 or i[0] != '#':
|
||||
raise GameError("Invalid color: {}".format(i[4:]))
|
||||
for j in i[4:]:
|
||||
if j not in '0123456789abcdefABCDEF':
|
||||
raise GameError("Invalid color: {}".format(i[4:]))
|
||||
bgc = i[4:]
|
||||
elif i[0:6].casefold() == 'shape=':
|
||||
if len(i[6:]) != 7 or i[0] != '#':
|
||||
raise GameError("Invalid color: {}".format(i[6:]))
|
||||
for j in i[6:]:
|
||||
if j not in '0123456789abcdefABCDEF':
|
||||
raise GameError("Invalid color: {}".format(i[6:]))
|
||||
shape = i[6:]
|
||||
elif i == 'persist':
|
||||
persist = True
|
||||
elif i[0:12].casefold() == 'description=':
|
||||
description = i[12:]
|
||||
elif i[0:8].casefold() == 'usefunc=':
|
||||
useFunc = i[8:]
|
||||
elif i[0:10].casefold() == 'useonfunc=':
|
||||
useOnFunc = i[10:]
|
||||
elif i.casefold() == 'ranged':
|
||||
ranged = True
|
||||
elif i[0:3].casefold() == 'cv:':
|
||||
equalsign = i.index('=')
|
||||
cv = i[3:equalsign]
|
||||
customValues[cv] = getValueFromString(i[equalsign+1:])
|
||||
thing = _gm.Item(name, x, y, description, useFunc, useOnFunc, customValues, ranged, (bgc, fgc, shape))
|
||||
self.nextThing = self.level.addThing(thing, self.nextThing, persist)
|
||||
|
||||
# behaviors
|
||||
|
||||
def wander(self, actor):
|
||||
|
@ -752,6 +1230,10 @@ always give the player a turn, False otherwise."""
|
|||
"""Registers a function for useFuncs and such to use."""
|
||||
self.__IOCalls[name] = func
|
||||
|
||||
def registerScript(self, name, func):
|
||||
"""Registers a function as a script callable from yaml files."""
|
||||
self.__scripts[name] = func
|
||||
|
||||
def getIO(self, name):
|
||||
"""This is so derived classes can access their IOCalls."""
|
||||
if name not in self.__IOCalls:
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue