gameshell/gamebase.py

1437 lines
63 KiB
Python
Raw Normal View History

# gamebase.py
import re as _re
import heapq as _hq
import gamemap as _gm
import gameevents as _ge
import random as _ra
import sys as _sys
import pickle as _pi
import ruamel.yaml as _yaml
2019-06-08 13:42:00 -04:00
import textwrap as _tw
class GameError(RuntimeError):
pass
2019-06-08 13:42:00 -04:00
class _ScriptBreak(object):
def __init__(self, value):
self.value = value
class GameBase(object):
2019-06-08 13:42:00 -04:00
coordRegex = _re.compile(r'(?:(-?[a-zA-Z]+) ?(-?[0-9]+))|(?:(-?[0-9]+),? (-?[0-9]+))|(?:\(([0-9]+), ([0-9]+)\))')
#coordRegex1 = _re.compile(r'(-?[a-zA-Z]+) ?(-?[0-9]+)') # "B12" or "B 12"
#coordRegex2 = _re.compile(r'\(([0-9]+), ([0-9]+)\)') # "(2, 12)"
#coordRegex3 = _re.compile(r'(-?[0-9]+),? (-?[0-9]+)') # "2 12" or "2, 12"
def __init__(self):
self.outstream = _sys.stdout
self.__useFuncs = {}
self.__behaviors = {}
self.__gameEvents = {}
self.__IOCalls = {} # {str : function}
2019-05-30 15:44:29 -04:00
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}}
self.ps2 = '? '
self.eventQueue = []
self.gameTime = 0.0
self.skipLoop = True
self.nextThing = 0
# player info
self.playerName = 'You'
self.playerDescription = 'The main character.'
self.player = None # reference to the player's 'thing'
# function deligates
self.onLevelLoad = None # str : level name? -> None
#self.onContainer = None # list : contents -> list : newContents, float : timeOpen
# default events and useFuncs
self.registerEvent('noop', self.handleNoOp)
self.registerEvent('go', self.handleGo)
self.registerEvent('arrive', self.handleArrive)
self.registerEvent('use', self.handleUse)
self.registerEvent('useon', self.handleUseOn)
self.registerEvent('take', self.handleTake)
self.registerEvent('drop', self.handleDrop)
self.registerEvent('behave', self.handleBehave)
self.registerUseFunc('examine', self.examine)
self.registerUseFunc('key', self.key)
self.registerUseFunc('container', self.container)
self.registerUseFunc('info', self.info)
self.registerUseFunc('wear', self.wear)
2019-06-08 13:42:00 -04:00
self.registerBehavior('stand', self.stand)
self.registerBehavior('wander', self.wander)
2019-05-30 15:44:29 -04:00
self.registerScript('if', self.ifScript)
self.registerScript('printf', self.printfScript)
2019-06-08 13:42:00 -04:00
self.registerScript('get', self.getCustomValue)
2019-05-30 15:44:29 -04:00
self.registerScript('set', self.setCustomValue)
2019-06-08 13:42:00 -04:00
self.registerScript('del', self.delCustomValue)
2019-05-30 15:44:29 -04:00
self.registerScript('spawn', self.spawnThing)
self.registerScript('give', self.giveToPlayer)
2019-06-08 13:42:00 -04:00
self.registerScript('move', self.moveThingScript)
self.registerScript('cvget', self.cvget)
self.registerScript('cvmod', self.cvmod)
self.registerScript('cvdel', self.cvdel)
# Helper functions
def requestInput(self, prompt = ''):
"""Like input by default, but should be overridden if you don't want
to get input from stdin."""
return input(prompt)
def letterToNumber(self, letter):
if letter.isdecimal():
return int(letter)
ret = 0
sign = 1
start = 0
for i in range(len(letter)):
if letter[i] == '-':
sign = -sign
start -= 1
elif letter[i].isalpha():
if letter[i].isupper():
ret += (ord(letter[i]) - ord('A')) * (26**(i+start))
else:
ret += (ord(letter[i]) - ord('a')) * (26**(i+start))
else:
return ret * sign
return ret * sign
def numberToLetter(self, number):
if isinstance(number, str):
return number
ret = ''
sign = ''
if number == 0:
return 'A'
elif number < 0:
sign = '-'
number = -number
while number > 0:
ret += chr(ord('A') + number % 26)
number = int(number / 26)
return sign + ret
def parseCoords(self, args, usePlayerCoords = True, allowInventory = True):
"""Takes an argument list, and figures out what it's talking about.
Returns (thing, x, y). "Thing" can be None."""
if self.level == None:
raise GameError('Coordinates cannot be parsed because there is no map.')
#print(coordStr)
x = -1
y = -1
2019-06-08 13:42:00 -04:00
# first, try by name for objects in the map
name = ' '.join(args)
thing = self.level.getThingByName(name)
if thing != None:
if usePlayerCoords:
return thing, thing.playerx, thing.playery
else:
return thing, thing.x, thing.y
# Second, try by name for objects in the inventory
if allowInventory:
if name in self.player.thingNames:
return self.player.getThingByName(name), -1, -1
2019-06-08 13:42:00 -04:00
# Third, try by location
coordStr = args[0]
if len(args) > 1:
coordStr += ' ' + args[1]
match = GameBase.coordRegex.match(coordStr)
if match != None: # if coordinates were given
#print(match.group())
groups = match.groups()
ind = 0
for i in range(len(groups)):
if groups[i] == None or groups[i] == match.group():
continue
else:
ind = i
break
#print(groups[ind], groups[ind+1])
x = self.letterToNumber(groups[ind])
y = self.letterToNumber(groups[ind+1])
#print(x, y)
thing = self.level.getThingAtCoords(x, y)
if thing != None and usePlayerCoords:
return thing, thing.playerx, thing.playery
else:
return thing, x, y
else: # if a name was given
2019-06-08 13:42:00 -04:00
return None, -1, -1
def justifyText(self, text, width = 80):
ret = []
text = text.lstrip().rstrip()
# start by making it all one long line.
text = _re.sub(r'\s{2,}', r' ', text)
# then add newlines as needed.
#i = 80
#i = get_terminal_size()[0]
i = width
while i < len(text):
while text[i] != ' ':
i -= 1
ret.append(text[:i])
text = text[i+1:]
ret.append(text)
return '\n'.join(ret)
2019-06-08 13:42:00 -04:00
def getValueFromString(self, arg: str, env = None):
if env == None:
env = self.customValues
2019-05-30 15:44:29 -04:00
val = None
validIdent = _re.match(r'[_A-Za-z][_0-9A-Za-z]*', arg)
2019-06-08 13:42:00 -04:00
if arg[0] in '"\'' and arg[-1] == arg[0]: # assume it's a string
2019-05-30 15:44:29 -04:00
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' and self.player != None:
val = self.player.x
elif arg.casefold() == 'playery' and self.player != None:
val = self.player.y
elif arg.casefold() == 'playername' and self.player != None:
val = self.player.name
elif arg.casefold() == 'playerinv' and self.player != None:
val = self.player.inventory
elif arg.casefold() == 'playerdesc' and self.player != None:
val = self.player.description
2019-05-30 15:44:29 -04:00
elif validIdent != None:
2019-06-08 13:42:00 -04:00
group = validIdent.group()
if 'scriptLocal' in self.customValues and group in self.customValues['scriptLocal']:
val = self.customValues['scriptLocal'][group]
elif group in env:
val = env[group]
2019-05-30 15:44:29 -04:00
else:
return False # for if statements; if a variable doesn't exist, should evaluate to False
# evaluate all values of all indecies
2019-06-08 13:42:00 -04:00
openBracket = validIdent.end()
if openBracket < len(arg):
if arg[openBracket] == '[':
2019-05-30 15:44:29 -04:00
ptr = openBracket
depth = 0
while ptr < len(arg):
2019-06-08 13:42:00 -04:00
if depth == 0 and arg[ptr] != '[':
raise GameError('Invalid value syntax: {}'.format(arg))
if arg[ptr] == '[':
2019-05-30 15:44:29 -04:00
depth += 1
2019-06-08 13:42:00 -04:00
elif arg[ptr] == ']':
2019-05-30 15:44:29 -04:00
depth -= 1
2019-06-08 13:42:00 -04:00
2019-05-30 15:44:29 -04:00
if depth == 0:
2019-06-08 13:42:00 -04:00
index = self.getValueFromString(arg[openBracket+1:ptr])
if index in val:
val = val[index]
else:
return False
2019-05-30 15:44:29 -04:00
openBracket = ptr + 1
ptr += 1
else:
raise GameError('Invalid value syntax: {}'.format(arg))
else:
raise GameError('Invalid argument to getValueFromString: {}'.format(arg))
return val
2019-06-08 13:42:00 -04:00
def compareValues(self, args: list):
"""Generalized comparisons, may eventually be extended to other operators"""
2019-06-08 13:42:00 -04:00
if len(args) == 1:
return bool(self.getValueFromString(args[0]))
elif len(args) == 3 and args[1] in ('==', '!=', '<=', '>=', '<', '>', 'in'):
lval = self.getValueFromString(args[0])
operator = args[1]
rval = self.getValueFromString(args[2])
if operator == '==':
return lval == rval
elif operator == '!=':
return lval != rval
elif operator == '<=':
return lval <= rval
elif operator == '>=':
return lval >= rval
elif operator == '<':
return lval < rval
elif operator == '>':
return lval > rval
elif operator == 'in':
if args[2].casefold() == 'playerinv':
return lval in self.player.thingNames
2019-06-08 13:42:00 -04:00
else:
return lval in rval
else:
raise GameError("Condition cannot be evaluated: {}".format(' '.join(args)))
2019-05-30 15:44:29 -04:00
# commands
def go(self, args):
"""go [-r] [to [the]] destination [additional arguments...]
Go to a location. "walk" and "move" are aliases of go.
-r: run to the location. "r" and "run" are aliases of -r.
The "run" command is an alias of "go -r".
"to" and "the" do nothing, and are intended only to make certain commands
make more sense from a linguistic perspective (for instance, one could
say "go to the balcony" rather than just "go balcony").
Destination can be a coordinate pair or object name. For instance, if one
wanted to go to D6, one could say "go to D6", "go to d 6", or "go to 3 6".
The letter is not case-sensitive."""
if self.level == None:
raise GameError("Cannot move: No level has been loaded.")
if self.player == None:
raise GameError("Cannot move: Player character doesn't exist.")
speed = 0.6666667
if args[0] == '-r' or args[0] == 'r' or args[0] == 'run':
speed = 0.3333333
args.pop(0)
if args[0] == 'to':
args.pop(0)
if args[0] == 'the':
args.pop(0)
thing, x, y = self.parseCoords(args, allowInventory = False)
# Now we have a heading! Let's see if we can get there...
if (x, y) == (self.player.x, self.player.y):
#_hq.heappush(self.eventQueue, (self.gameTime, _ge.ArriveEvent(self.playerName, x, y, 0.0)))
self.setEvent(0.0, _ge.ArriveEvent(self.player, self.player.x, self.player.y, 0.0))
return
dist, path = self.level.path(x, y, self.player.x, self.player.y)
if dist == -1:
print('{0} cannot reach {1}{2}.'.format(self.player.name, self.numberToLetter(x), y), file = self.outstream)
#_hq.heappush(self.eventQueue, (self.gameTime, _ge.NoOpEvent()))
return
else:
pos = self.level.coordsToInt(self.player.x, self.player.y)
space = path[pos]
if space == -1:
self.setEvent(0.0, _ge.ArriveEvent(self.player, self.player.x, self.player.y, 0.0))
return
#target = self.level.coordsToInt(x, y)
t = 1
#while space != target:
while space != -1:
newx, newy = self.level.intToCoords(space)
space = path[space]
if space != -1:
self.setEvent(t * speed, _ge.GoEvent(self.player, newx, newy))
else:
self.setEvent(t * speed, _ge.ArriveEvent(self.player, newx, newy, t * speed))
break
t += 1
#newx, newy = self.level.intToCoords(space)
#_hq.heappush(self.eventQueue, (self.gameTime + t * speed, _ge.ArriveEvent(self.player, newx, newy, t * speed)))
return
def look(self, args):
"""look [at [the]] object
Describe an object.
"at" and "the" do nothing, and are intended only to make certain commands
make more sense from a linguistic perspective (for instance, one could
say "look at the balcony" rather than just "look balcony").
Object can be the name of the object, or its coordinates."""
if self.level == None:
raise GameError("Cannot look: No level has been loaded.")
if self.player == None:
raise GameError("Cannot look: Player character doesn't exist.")
if len(args) == 0:
print(self.justifyText(self.level.description), file = self.outstream)
else:
if args[0] == 'at':
args.pop(0)
if args[0] == 'the':
args.pop(0)
thing, x, y = self.parseCoords(args, usePlayerCoords = False)
if not self.level.lineOfSight(self.player.x, self.player.y, x, y):
print("{} cannot see that.".format(self.player.name), file = self.outstream)
elif thing == None:
print("There is nothing to see here.\n", file = self.outstream)
else:
print(self.justifyText(str(thing)), file = self.outstream)
#_hq.heappush(self.eventQueue, (self.gameTime, _ge.NoOpEvent()))
def talk(self, args):
"""talk [to [the]] character
Talk to a character.
"to" and "the" do nothing, and are intended only to make certain commands
make more sense from a linguistic perspective (for instance, one could
say "talk to the guard" rather than just "talk guard").
Character can be the name of the character, or their coordinates."""
if self.level == None:
raise GameError("Cannot talk: No level has been loaded.")
if self.player == None:
raise GameError("Cannot talk: Player character doesn't exist.")
if len(args) == 0:
print(self.justifyText(self.level.description), file = self.outstream)
else:
if args[0] == 'to':
args.pop(0)
if args[0] == 'the':
args.pop(0)
2019-06-08 13:42:00 -04:00
thing, x, y = self.parseCoords(args, usePlayerCoords = False, allowInventory = False)
if not self.level.lineOfSight(self.player.x, self.player.y, x, y):
print("{} cannot talk to {}.".format(self.player.name, thing.name), file = self.outstream)
elif thing == None:
print("There is nobody here.\n", file = self.outstream)
else:
if 'dialogs' in thing.customValues:
if isinstance(thing.customValues['dialogs'], list):
if 'dialogPtr' in thing.customValues and isinstance(thiong.customValues['dialogPtr'], int):
self.dialog(thing.customValues['dialogs'][thing.customValues['dialogPtr']], thing)
else:
self.dialog(thing.customValues['dialogs'][0], thing)
elif isinstance(thing.customValues['dialogs'], str):
self.dialog(thing.customValues['dialogs'], thing)
else:
raise GameError("Character '{}' has dialog of an unexpected type.".format(thing.name))
else:
print("{} has nothing to say to you.".format(thing.name), file = self.outstream)
def use(self, args):
"""use [-r] [the] object [on [the] object2]
Use an object. If the player is not already close to it, they will go to it.
use [-r] [the] item on [the] object
Use an item from the player's inventory on an object.
-r: run to the location. "r" and "run" are aliases of -r.
"the" does nothing, and is intended only to make certain commands
make more sense from a linguistic perspective (for instance, one could
say "use the lever" rather than just "use lever").
Object can be the name of the object, or its coordinates. It can also be
the name of an item in the player's inventory."""
if self.level == None:
raise GameError("Cannot use: No level has been loaded.")
if self.player == None:
raise GameError("Cannot use: Player character doesn't exist.")
speed = 0.6666667
useArgs = []
if args[0] == '-r' or args[0] == 'r' or args[0] == 'run':
speed = 0.3333333
args.pop(0)
if args[0] == 'the':
args.pop(0)
if 'on' in args:
self.useOn(args, speed)
return
if 'with' in args:
useArgs = args[args.index('with')+1:]
thing, x, y = self.parseCoords(args)
if thing == None:
print("There is nothing to use.", file = self.outstream)
#_hq.heappush(self.eventQueue, (self.gameTime, _ge.NoOpEvent()))
return
if thing.thingType != 'u' and thing not in self.player.inventory:
print("The {0} cannot be used.".format(thing.name), file = self.outstream)
#_hq.heappush(self.eventQueue, (self.gameTime, _ge.NoOpEvent()))
return
# Similar to go, but not quite the same.
if (x, y) == (self.player.x, self.player.y) or thing in self.player.inventory:
self.setEvent(0.125, _ge.UseEvent(thing, useArgs))
return
dist, path = self.level.path(x, y, self.player.x, self.player.y)
if dist == -1:
print('{0} cannot reach the {1}.'.format(self.player.name, thing.name), file = self.outstream)
#_hq.heappush(self.eventQueue, (self.gameTime, _ge.NoOpEvent()))
return
else:
pos = self.level.coordsToInt(self.player.x, self.player.y)
space = path[pos]
#target = self.level.coordsToInt(x, y)
t = 1
#while space != target:
while space != -1:
newx, newy = self.level.intToCoords(space)
space = path[space]
self.setEvent(t * speed, _ge.GoEvent(self.player, newx, newy))
t += 1
#newx, newy = self.level.intToCoords(space)
#_hq.heappush(self.eventQueue, (self.gameTime + t * speed, _ge.GoEvent(self.playerName, newx, newy)))
self.setEvent(t * speed + 0.125, _ge.UseEvent(thing, useArgs))
return
def useOn(self, args, speed):
"""Called by use when there is an 'on' clause"""
onIndex = args.index('on')
2019-06-08 13:42:00 -04:00
item, x, y = self.parseCoords(args[:onIndex], usePlayerCoords = False, allowInventory = True)
if args[onIndex+1] == 'the':
onIndex += 1
2019-06-08 13:42:00 -04:00
thing, x, y = self.parseCoords(args[onIndex+1:], usePlayerCoords = True, allowInventory = True)
useArgs = []
if 'with' in args:
useArgs = args[args.index('with')+1:]
if item == None or item not in self.player.inventory:
2019-06-08 13:42:00 -04:00
print("There is no such item in the inventory.", file = self.outstream)
#_hq.heappush(self.eventQueue, (self.gameTime, _ge.NoOpEvent()))
return
if thing == None and x < 0 and y < 0:
print("Argument contains 'to' but with no real predicate.", file = self.outstream)
#_hq.heappush(self.eventQueue, (self.gameTime, _ge.NoOpEvent()))
return
if not item.ranged:
# Similar to go, but not quite the same.
if (x, y) == (self.player.x, self.player.y):
self.setEvent(0.125, _ge.UseOnEvent(item, thing, useArgs))
return
dist, path = self.level.path(x, y, self.player.x, self.player.y)
if dist == -1:
if thing != None:
print('{0} cannot reach the {1}.'.format(self.player.name, thing.name), file = self.outstream)
else:
print('{0} cannot reach {1}{2}.'.format(self.player.name, self.numberToLetter(x), y), file = self.outstream)
#_hq.heappush(self.eventQueue, (self.gameTime, _ge.NoOpEvent()))
return
else:
pos = self.level.coordsToInt(self.player.x, self.player.y)
space = path[pos]
#target = self.level.coordsToInt(x, y)
t = 1
#while space != target:
while space != -1:
newx, newy = self.level.intToCoords(space)
space = path[space]
self.setEvent(t * speed, _ge.GoEvent(self.player, newx, newy))
t += 1
#newx, newy = self.level.intToCoords(space)
#_hq.heappush(self.eventQueue, (self.gameTime + t * speed, _ge.GoEvent(self.playerName, newx, newy)))
self.setEvent(t * speed + 0.125, _ge.UseOnEvent(item, thing, useArgs))
else:
if not self.level.lineOfSight(self.player.x, self.player.y, x, y):
print("{} cannot use the {} on that from here.".format(self.player.name, item.name), file = self.outstream)
elif thing == None:
print("There is nothing to use the {} on here.".format(item.name), file = self.outstream)
else:
self.setEvent(0, _ge.UseOnEvent(item, thing, useArgs))
return
def take(self, args):
"""take [the] item
Take an item. 'get' is an alias of take.
If the player is not already close to it, they will go to it.
"the" does nothing, and is intended only to make certain commands
make more sense from a linguistic perspective (for instance, one could
say "take the flask" rather than just "take flask").
Object can be the name of the object, or its coordinates."""
if self.level == None:
raise GameError("Cannot take: No level has been loaded.")
if self.player == None:
raise GameError("Cannot take: Player character doesn't exist.")
speed = 0.6666667
if args[0] == '-r' or args[0] == 'r' or args[0] == 'run':
speed = 0.3333333
args.pop(0)
if args[0] == 'the':
args.pop(0)
2019-06-08 13:42:00 -04:00
thing, x, y = self.parseCoords(args, allowInventory = False)
if thing == None:
print("There is nothing to take.", file = self.outstream)
return
if thing.thingType != 'i':
print("The {0} cannot be taken.".format(thing.name), file = self.outstream)
#_hq.heappush(self.eventQueue, (self.gameTime, _ge.NoOpEvent()))
return
# Similar to go, but not quite the same.
if (x, y) == (self.player.x, self.player.y):
self.setEvent(0.125, _ge.TakeEvent(self.player, thing))
return
dist, path = self.level.path(x, y, self.player.x, self.player.y)
if dist == -1:
print('{0} cannot reach the {1}.'.format(self.player.name, thing.name), file = self.outstream)
#_hq.heappush(self.eventQueue, (self.gameTime, _ge.NoOpEvent()))
return
else:
pos = self.level.coordsToInt(self.player.x, self.player.y)
space = path[pos]
#target = self.level.coordsToInt(x, y)
t = 1
#while space != target:
while space != -1:
newx, newy = self.level.intToCoords(space)
space = path[space]
self.setEvent(t * speed, _ge.GoEvent(self.player, newx, newy))
t += 1
#newx, newy = self.level.intToCoords(space)
#_hq.heappush(self.eventQueue, (self.gameTime + t * speed, _ge.GoEvent(self.player.name, newx, newy)))
self.setEvent(t * speed + 0.125, _ge.TakeEvent(self.player, thing))
return
def drop(self, args):
"""drop [the] item"""
if self.level == None:
raise GameError("Cannot drop: No level has been loaded.")
if self.player == None:
raise GameError("Cannot drop: Player character doesn't exist.")
if args[0] == 'the':
args.pop(0)
#for i in self.player.inventory:
# thingName = self.player.inventory[i].name
# if ' '.join(args) == thingName:
# self.setEvent(0.0, _ge.DropEvent(self.player.inventory[i]))
# return
thingName = ' '.join(args)
if thingName in self.player.thingNames:
self.setEvent(0.0, _ge.DropEvent(self.player.getThingByName(thingName)))
else:
print('{0} does not have a {1}.'.format(self.player.name, args[0]), file = self.outstream)
def loadMap(self, args):
# loadMap (fileName)
# loadMap (fileName, entrance)
# loadMap (fileName, x, y)
# save persistent things in previous level
if self.level != None:
if self.level.name not in self.persist:
self.persist[self.level.name] = {}
for i in self.level.persistent:
self.persist[self.level.name][i] = self.level.getThingByID(i)
preLoaded = False
if args[0] in self.persist:
preLoaded = True
# load the new level
if len(args) == 2:
self.level, self.nextThing = _gm.GameMap.read(args[0], int(args[1]), preLoaded, self.nextThing)
else:
self.level, self.nextThing = _gm.GameMap.read(args[0], None, preLoaded, self.nextThing)
2019-06-08 13:42:00 -04:00
if self.level == None:
raise GameError("Map could not be loaded.")
# get persistent things from it
if args[0] in self.persist:
persistedThings = tuple(self.persist[args[0]].keys())
for i in persistedThings:
2019-06-08 13:42:00 -04:00
self.nextThing = self.level.addThing(self.persist[args[0]][i], self.nextThing, True) #nextThing shouldn't change
del self.persist[args[0]][i] # delete them from the persist dict to prevent item duplication
print(self.level.openingText, file = self.outstream)
#print(self.outstream.getvalue())
x, y = self.level.playerStart
if len(args) == 3:
x, y = int(args[1]), int(args[2])
if self.player == None:
# create a player to put in the level.
self.player = _gm.PlayerCharacter(x, y, self.playerDescription, {}, {}, self.playerName)
print("Player created.")
else:
self.player.x, self.player.y = x, y
print("Player moved.")
self.player.prevx, self.player.prevy = self.player.x, self.player.y
self.nextThing = self.level.addThing(self.player, self.nextThing) # The player needs to be added to the new level.
if self.onLevelLoad != None:
self.onLevelLoad()
2019-05-30 15:44:29 -04:00
self.parseScript(self.level.enterScript)
def unloadMap(self, args):
"""Args is only there just in case."""
self.level = None
self.player = None
def saveGame(self, args):
if len(args) < 1:
print("Save file must have a name!", file = self.outstream)
return
# choose pickle protocol depending on python version:
# 3 for Python 3.0.0 to 3.3.x, 4 for Python 3.4.0 to 3.7.x
prot = _pi.HIGHEST_PROTOCOL
# save persistent things so that the current level can be recalled as-is
if self.level != None:
if self.level.name not in self.persist:
self.persist[self.level.name] = {}
for i in self.level.persistent:
2019-06-08 13:42:00 -04:00
self.persist[self.level.name][i] = self.level.getThingByID(i)
# build data object to be saved
data = (self.player, self.level.name, self.persist, self.eventQueue, self.customValues, self.gameTime, self.nextThing)
# save it!
fileName = 'saves/' + args[0].replace(' ', '_') + '.dat'
if args[0].endswith('.dat'): # This is really for absolute paths, but doesn't really check for that.
fileName = args[0]
with open(fileName, 'wb') as f:
_pi.dump(data, f, protocol=prot)
# delete things in the current map from the persist dict to prevent item duplication
persistedThings = tuple(self.persist[self.level.name].keys())
for i in persistedThings:
del self.persist[self.level.name][i]
# push a no-op event so that saving doesn't cost player characters time
#_hq.heappush(self.eventQueue, (self.gameTime, _ge.NoOpEvent()))
return
def loadGame(self, args):
if len(args) < 1:
print("Save file must have a name!", file = self.outstream)
return
# choose pickle protocol depending on python version:
# 3 for Python 3.0.0 to 3.3.x, 4 for Python 3.4.0 to 3.7.x
prot = _pi.HIGHEST_PROTOCOL
fileName = 'saves/' + args[0].replace(' ', '_') + '.dat'
if args[0].endswith('.dat'):
fileName = args[0]
x, y, levelname = 1, 1, 'testing/test1.txt'
with open(fileName, 'rb') as f:
self.player, levelname, self.persist, self.eventQueue, self.customValues, self.gameTime, self.nextThing = _pi.load(f)
#print(levelname, x, y, file = self.outstream)
self.loadMap((levelname, self.player.x, self.player.y))
#_hq.heappush(self.eventQueue, (self.gameTime, _ge.NoOpEvent()))
return
2019-05-30 15:44:29 -04:00
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)
2019-05-30 15:44:29 -04:00
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;}{':
2019-06-08 13:42:00 -04:00
if l > 0 and instr[c-1] == '\\':
del literalStr[l-1]
2019-05-30 15:44:29 -04:00
l -= 1
2019-06-08 13:42:00 -04:00
else:
if argStart != l:
ret.append(''.join(literalStr[argStart:l]))
if instr[c] == ';':
if len(ret) > 0:
script.append(ret)
ret = []
elif instr[c] == '{': # block
stack.append(script)
2019-05-30 15:44:29 -04:00
script.append(ret)
2019-06-08 13:42:00 -04:00
script = []
ret.append(script)
ret = []
del literalStr[l]
l -= 1
elif instr[c] == '}': # close block
if len(ret) > 0:
script.append(ret)
ret = []
script = stack.pop()
del literalStr[l]
l -= 1
argStart = l + 1
2019-05-30 15:44:29 -04:00
c += 1
l += 1
if argStart != l:
ret.append(''.join(literalStr[argStart:l]))
2019-06-08 13:42:00 -04:00
if len(ret) > 0:
script.append(ret)
2019-05-30 15:44:29 -04:00
#print('After parsing: {}'.format(script))
self.customValues['scriptLocal'] = {}
2019-06-08 13:42:00 -04:00
ret = self.runScript(script)
if 'scriptLocal' in self.customValues:
del self.customValues['scriptLocal']
if isinstance(ret, _ScriptBreak):
ret = ret.value
return ret
2019-05-30 15:44:29 -04:00
def runScript(self, script: list):
"""run a script"""
2019-06-08 13:42:00 -04:00
ret = False
2019-05-30 15:44:29 -04:00
for line in script:
2019-06-08 13:42:00 -04:00
if len(line) == 0:
# empty line
continue
elif line[0].casefold() == 'playercmd':
2019-05-30 15:44:29 -04:00
# run a player command
self.getIO('playercmd')(line[1:])
2019-06-08 13:42:00 -04:00
elif line[0].casefold() == 'break':
# exit early
return _ScriptBreak(ret)
elif line[0] in self.__scripts:
# run a script
ret = self.__scripts[line[0]](line[1:])
if isinstance(ret, _ScriptBreak):
return ret
2019-05-30 15:44:29 -04:00
else:
2019-06-08 13:42:00 -04:00
# conditional evaluation
ret = self.compareValues(line)
# We specifically want the return value of the last line executed.
return ret
2019-05-30 15:44:29 -04:00
def gameEventLoop(self):
#print(self.skipLoop)
if self.skipLoop:
return
#print(self.skipLoop)
while len(self.eventQueue) > 0:
ev = _hq.heappop(self.eventQueue)
self.gameTime = ev[0]
e = ev[1]
if self.__gameEvents[e.eventType](e):
#print('break loop')
break
if len(self.eventQueue) == 0:
self.gameTime = 0.0
_ge.resetEventNum()
self.skipLoop = True
def setEvent(self, t, e, skip = False):
_hq.heappush(self.eventQueue, (self.gameTime + t, e))
self.skipLoop = skip
# default event handlers
def handleNoOp(self, e):
return True
def handleGo(self, e):
#if e.actor == self.playerName:
# self.prevx, self.prevy = self.playerx, self.playery
# self.playerx, self.playery = e.x, e.y
#else:
if e.actor == None:
raise GameError("'Go' event raised for no object.")
self.level.moveThing(e.actor, e.x, e.y)
return False
def handleArrive(self, e):
#if e.actor == self.playerName:
# self.prevx, self.prevy = self.playerx, self.playery
# self.playerx, self.playery = e.x, e.y
# print('{0} arrived at {1}{2} after {3:.1f} seconds.'.format(self.playerName, self.numberToLetter(e.x), e.y, e.t), file = self.outstream)
# thing = self.level.getThingAtCoords(self.playerx, self.playery)
# if thing:
# if thing.thingType == 'x':
# self.parseScript(thing.onUse)
# if (isinstance(thing.key, bool) and thing.key == True) or (isinstance(thing.key, str) and self.parseScript(thing.key)):
# a = self.requestInput('Do you want to go {0}? (Y/n)'.format(str(thing)))
# if a != 'n' and a != 'N':
# self.loadMap((thing.destination, thing.exitid))
# return True
#else:
# actor = self.level.getThingByName(e.actor)
# if actor:
# self.level.moveThing(e.actor, e.x, e.y)
# print('{0} arrived at {1}{2} after {3:.1f} seconds.'.format(actor.name, self.numberToLetter(e.x), e.y, e.t), file = self.outstream)
# thing = self.level.getThingAtCoords(actor.x, actor.y)
# if thing and thing != actor:
# if thing.thingType == 'x':
# print('{0} went {1}.'.format(actor.name, str(thing)))
# self.level.removeThing(actor.name)
# else:
# print('There is nothing by the name of {0}.'.format(e.actor), file = self.outstream)
# return False
if e.actor == None:
raise GameError("'Go' event raised for no object.")
thing = self.level.getThingAtCoords(e.x, e.y)
self.level.moveThing(e.actor, e.x, e.y)
print('{0} arrived at {1}{2} after {3:.1f} seconds.'.format(e.actor.name, self.numberToLetter(e.x), e.y, e.t), file = self.outstream)
if thing:
if thing.thingType == 'x':
if e.actor == self.player:
2019-06-08 13:42:00 -04:00
self.parseScript(thing.onUse)
if (isinstance(thing.key, bool) and thing.key == True) or (isinstance(thing.key, str) and self.parseScript(thing.key)):
a = self.requestInput('Do you want to go {0}? (Y/n)'.format(str(thing)))
if a != 'n' and a != 'N':
self.loadMap((thing.destination, thing.exitid))
else:
print('{0} went {1}.'.format(actor.name, str(thing)))
self.level.removeThing(actor.name)
return True
def handleUse(self, e):
if e.thing.useFunc == '':
print('The {0} cannot be used by itself.'.format(e.thing.name), file = self.outstream)
return True
self.setEvent(self.__useFuncs[e.thing.useFunc](e.thing, e.args), _ge.NoOpEvent())
return False
def handleUseOn(self, e):
if e.item.useOnFunc == '':
print('The {0} cannot be used on other objects.'.format(e.item.name), file = self.outstream)
return True
self.setEvent(self.__useFuncs[e.item.useOnFunc](e.item, e.thing, e.args), _ge.NoOpEvent())
return False
def handleTake(self, e):
if e.actor == None or e.actor.thingType not in 'np' or e.item == None:
raise GameError("'Take' event cannot be handled.")
self.level.removeThingByID(e.item.thingID)
e.actor.addThing(e.item)
return True
def handleDrop(self, e):
if e.actor == None or e.actor.thingType not in 'np':
raise GameError("'Drop' event cannot be handled.")
e.item.x = e.actor.x
e.item.y = e.actor.y
2019-06-08 13:42:00 -04:00
self.nextThing = self.level.addThing(e.item, self.nextThing, True) # nextThing shouldn't change
self.actor.removeThing(e.item)
return True
def handleBehave(self, e):
self.__behaviors[e.actor.behavior](e.actor)
# default useFuncs: take a list of arguments, return the time the use took
def examine(self, thing, args):
"""Just prints the given text."""
if 'pattern' not in thing.customValues or 'text' not in thing.customValues:
raise ValueError("Non-examinable thing {0} examined.".format(thing.name))
if thing.customValues['pattern'] == 'single':
2019-06-08 13:42:00 -04:00
print(self.justifyText(str(thing.customValues['text'])), file = self.outstream)
elif thing.customValues['pattern'] == 'loop':
if not 'cursor' in thing.customValues:
thing.customValues['cursor'] = 0
cursor = thing.customValues['cursor']
2019-06-08 13:42:00 -04:00
print(self.justifyText(str(thing.customValues['text'][cursor])), file = self.outstream)
thing.customValues['cursor'] = (cursor + 1) % len(thing.customValues['text'])
elif thing.customValues['pattern'] == 'once':
if not 'cursor' in thing.customValues:
thing.customValues['cursor'] = 0
cursor = thing.customValues['cursor']
2019-06-08 13:42:00 -04:00
print(self.justifyText(str(thing.customValues['text'][cursor])), file = self.outstream)
if cursor < len(thing.customValues['text']) - 1:
thing.customValues['cursor'] += 1
elif thing.customValues['pattern'] == 'random':
cursor = _ra.randrange(len(thing.customValues['text']))
2019-06-08 13:42:00 -04:00
print(self.justifyText(str(thing.customValues['text'][cursor])), file = self.outstream)
thing.customValues['cursor'] = cursor
if 'set' in thing.customValues:
if 'cursor' in thing.customValues:
2019-06-08 13:42:00 -04:00
self.customValues[thing.customValues['set']] = thing.customValues[cursor]
else:
2019-06-08 13:42:00 -04:00
self.customValues[thing.customValues['set']] = True
return 0.0
def container(self, thing, args):
"""Acts as a container. Items can be traded between the container and the player's inventory."""
items = list(thing.customValues['items'])
thing.customValues['items'], timeOpen = self.getIO('container')(self.player, items)
return timeOpen
def info(self, thing, args):
"""Acts as a bookshelf, filing cabinet, bulletin board, or anything that contains raw info."""
items = dict(thing.customValues['items'])
return self.getIO('info')(items)
def wear(self, item, args):
"""Wear clotherg or otherwise equip a passive item."""
# An item must be in the player's inventory in order to wear it.
inInv = item in self.player.inventory
if not inInv:
print("You cannot wear what you are not carrying.", file = self.outstream)
return 0.0
if 'wearing' not in self.customValues:
self.customValues['wearing'] = {}
if item.customValues['slot'] not in self.customValues['wearing']:
self.customValues['wearing'][item.customValues['slot']] = self.player.removeThing(item)
# This is so a player can't put on a garment, then drop it while
# still also wearing it.
print("{} put on the {}.".format(self.player.name, item.name), file = self.outstream)
else: # the player must be wearing something that will get in the way
# put the old clothes back in the inventory
self.player.addThing(self.customValues['wearing'][item.customValues['slot']])
self.customValues['wearing'][item.customValues['slot']] = self.player.removeThing(item)
# This is so a player can't put on a garment, then drop it while
# still also wearing it.
print("{} took off the {} and put on the {}.".format(self.player.name, oldItem.name, item.name), file = self.outstream)
return 1.0
# item use-on functions
def key(self, item, thing, args):
"""Item is a key, which unlocks a door. This may be implemented for containers later."""
if isinstance(thing, tuple) or thing.thingType != 'd':
print("That is not a door.", file = self.outstream)
return 0.0
if thing.lock(item.name):
print("The key fits the lock.", file = self.outstream)
if not thing.passable and self.player.x == thing.x and self.player.y == thing.y:
self.player.x, self.player.y = self.player.prevx, self.player.prevy
else:
print("The key doesn't fit that lock.", file = self.outstream)
return 0.125
2019-05-30 15:44:29 -04:00
# 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
2019-06-08 13:42:00 -04:00
if len(args) > 1 and args[1] in ('==', '!=', '<=', '>=', '<', '>', 'in'):
2019-05-30 15:44:29 -04:00
if len(args) < 4:
raise GameError('Incomplete If statement: if {}'.format(' '.join(args)))
2019-06-08 13:42:00 -04:00
ret = self.compareValues(args[:3])
args = args[3:]
2019-05-30 15:44:29 -04:00
else:
2019-06-08 13:42:00 -04:00
ret = bool(self.getValueFromString(args[0]))
2019-05-30 15:44:29 -04:00
args = args[1:]
if inverse:
ret = not ret
# if condition is true, evaluate further
if ret:
if isinstance(args[-1], list):
2019-06-08 13:42:00 -04:00
return self.runScript(args[-1])
2019-05-30 15:44:29 -04:00
else:
2019-06-08 13:42:00 -04:00
return self.runScript([args])
2019-05-30 15:44:29 -04:00
def printfScript(self, args):
"""Assume the first argument is a stringable object, and format all others."""
2019-06-08 13:42:00 -04:00
ret = args[0].format(*[self.getValueFromString(i) for i in args[1:]])
print(ret, file = self.outstream)
return ret
def getCustomValue(self, args, env = None):
norm = True
if env == None:
env = self.customValues
else:
norm = False
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)
return self.getValueFromString(args[0], env)
2019-05-30 15:44:29 -04:00
2019-06-08 13:42:00 -04:00
def setCustomValue(self, args, env = None):
2019-05-30 15:44:29 -04:00
"""takes [customValue, op, value]"""
2019-06-08 13:42:00 -04:00
if env == None:
env = self.customValues
2019-05-30 15:44:29 -04:00
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.')
2019-06-08 13:42:00 -04:00
# This next line allows cvmod to use values in the thing's customValues dict,
# but doing this means that accessing a game CV requires setting it to a local var.
val = self.getValueFromString(args[2], env)
2019-05-30 15:44:29 -04:00
# 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
2019-06-08 13:42:00 -04:00
return env[args[0]]
def delCustomValue(self, args, env = None):
"""To clean up after a map."""
if env == None:
env = self.customValues
for i in args:
if i in env:
del(env[i])
2019-05-30 15:44:29 -04:00
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:]
2019-06-08 13:42:00 -04:00
elif i[0:9].casefold() == 'location=':
_, x, y = self.parseCoords(i[9:].split(), usePlayerCoords = False, allowInventory = False)
2019-05-30 15:44:29 -04:00
elif i[0:4].casefold() == 'fgc=':
2019-06-08 13:42:00 -04:00
if len(i[4:]) != 7 or i[4] != '#':
2019-05-30 15:44:29 -04:00
raise GameError("Invalid color: {}".format(i[4:]))
2019-06-08 13:42:00 -04:00
for j in i[5:]:
2019-05-30 15:44:29 -04:00
if j not in '0123456789abcdefABCDEF':
raise GameError("Invalid color: {}".format(i[4:]))
fgc = i[4:]
elif i[0:4].casefold() == 'bgc=':
2019-06-08 13:42:00 -04:00
if len(i[4:]) != 7 or i[4] != '#':
2019-05-30 15:44:29 -04:00
raise GameError("Invalid color: {}".format(i[4:]))
2019-06-08 13:42:00 -04:00
for j in i[5:]:
2019-05-30 15:44:29 -04:00
if j not in '0123456789abcdefABCDEF':
raise GameError("Invalid color: {}".format(i[4:]))
bgc = i[4:]
elif i[0:6].casefold() == 'shape=':
2019-06-08 13:42:00 -04:00
if i[6] not in 'ox^ #|-':
raise GameError("Invalid shape: {}".format(i[6]))
shape = i[6]
2019-05-30 15:44:29 -04:00
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.
2019-06-08 13:42:00 -04:00
thing = None
2019-05-30 15:44:29 -04:00
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 bgc == None:
2019-06-08 13:42:00 -04:00
bgc = _gm.Item.defaultGraphic[0]
if fgc == None:
fgc = _gm.Item.defaultGraphic[1]
2019-05-30 15:44:29 -04:00
if shape == None:
2019-06-08 13:42:00 -04:00
shape = _gm.Item.defaultGraphic[2]
2019-05-30 15:44:29 -04:00
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]
2019-06-08 13:42:00 -04:00
customValues[cv] = self.getValueFromString(i[equalsign+1:])
2019-05-30 15:44:29 -04:00
thing = _gm.Item(name, x, y, description, useFunc, useOnFunc, customValues, ranged, (bgc, fgc, shape))
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 bgc == None:
2019-06-08 13:42:00 -04:00
bgc = _gm.Useable.defaultGraphic[0]
if fgc == None:
fgc = _gm.Useable.defaultGraphic[1]
2019-05-30 15:44:29 -04:00
if shape == None:
2019-06-08 13:42:00 -04:00
shape = _gm.Useable.defaultGraphic[2]
2019-05-30 15:44:29 -04:00
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]
2019-06-08 13:42:00 -04:00
customValues[cv] = self.getValueFromString(i[equalsign+1:])
2019-05-30 15:44:29 -04:00
thing = _gm.Useable(name, x, y, description, useFunc, customValues, playerx, playery, (bgc, fgc, shape))
elif args[0].casefold() == 'npc':
# spawn an NPC
description = 'A nondescript character.'
2019-06-08 13:42:00 -04:00
behavior = 'stand'
2019-05-30 15:44:29 -04:00
inv = []
customValues = {}
playerx, playery = x, y
if name == None:
name = 'character {}'.format(self.nextThing)
if bgc == None:
2019-06-08 13:42:00 -04:00
bgc = _gm.NPC.defaultGraphic[0]
if fgc == None:
fgc = _gm.NPC.defaultGraphic[1]
2019-05-30 15:44:29 -04:00
if shape == None:
2019-06-08 13:42:00 -04:00
shape = _gm.NPC.defaultGraphic[2]
2019-05-30 15:44:29 -04:00
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]
2019-06-08 13:42:00 -04:00
customValues[cv] = self.getValueFromString(i[equalsign+1:])
thing = _gm.NPC(name, x, y, description, behavior, inv, customValues, playerx, playery, (bgc, fgc, shape))
2019-05-30 15:44:29 -04:00
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 bgc == None:
2019-06-08 13:42:00 -04:00
bgc = _gm.Door.defaultGraphic[0]
if fgc == None:
fgc = _gm.Door.defaultGraphic[1]
2019-05-30 15:44:29 -04:00
if shape == None:
2019-06-08 13:42:00 -04:00
shape = _gm.Door.defaultGraphic[2]
2019-05-30 15:44:29 -04:00
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))
elif args[0].casefold() == 'mapexit':
# spawn an exit to another map (use with EXTREME caution!)
destination = ''
prefix = None
exitid = 0
2019-06-08 13:42:00 -04:00
onUse = ''
key = True
2019-05-30 15:44:29 -04:00
if name == None:
name = 'somewhere'
if bgc == None:
2019-06-08 13:42:00 -04:00
bgc = _gm.MapExit.defaultGraphic[0]
if fgc == None:
fgc = _gm.MapExit.defaultGraphic[1]
2019-05-30 15:44:29 -04:00
if shape == None:
2019-06-08 13:42:00 -04:00
shape = _gm.MapExit.defaultGraphic[2]
2019-05-30 15:44:29 -04:00
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:])
2019-06-08 13:42:00 -04:00
elif i[0:6].casefold() == 'onuse=':
onUse = i[6:]
elif i[0:4].casefold() == 'key=':
key = i[4:]
thing = _gm.MapExit(name, x, y, exitid, destination, prefix, onUse, key, (bgc, fgc, shape))
2019-05-30 15:44:29 -04:00
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)
2019-06-08 13:42:00 -04:00
else:
raise GameError("{} not a valid thing type.".format(args[0]))
self.nextThing = self.level.addThing(thing, self.nextThing, persist)
return thing
2019-05-30 15:44:29 -04:00
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=':
2019-06-08 13:42:00 -04:00
_, x, y = self.parseCoords(i[10:].split(), usePlayerCoords = False, allowInventory = False)
2019-05-30 15:44:29 -04:00
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))
2019-06-08 13:42:00 -04:00
thing.thingID = self.nextThing
self.player.addThing(thing)
2019-06-08 13:42:00 -04:00
self.nextThing += 1
return thing
def moveThingScript(self, args):
colon = args.index(':')
thing, x, y = self.parseCoords(args[0:colon], usePlayerCoords = False, allowInventory = False)
if thing == None:
return False
_, newX, newY = self.parseCoords(args[colon+1:], usePlayerCoords = True, allowInventory = False)
self.level.moveThing(thing, newX, newY)
def cvget(self, args):
thing, x, y = self.parseCoords(args[0:-1], usePlayerCoords = False, allowInventory = True)
if thing != None and thing.thingType in 'ciu':
return self.getCustomValue(args[-1:], env = thing.customValues)
else:
raise GameError("Thing described in cvmod doesn't exist or isn't a character, item, or useable.")
def cvmod(self, args):
"""Modify a custom value in a thing.
args = thing identifier operator value"""
thing, x, y = self.parseCoords(args[0:-3], usePlayerCoords = False, allowInventory = True)
if thing != None and thing.thingType in 'ciu':
return self.setCustomValue(args[-3:], env = thing.customValues)
else:
raise GameError("Thing described in cvmod doesn't exist or isn't a character, item, or useable.")
def cvdel(self, args):
"""Delete custom values."""
colon = args.index(':')
thing, x, y = self.parseCoords(args[0:colon], usePlayerCoords = False, allowInventory = True)
if thing != None and thing.thingType in 'ciu':
return self.setCustomValue(args[colon+1:], env = thing.customValues)
else:
raise GameError("Thing described in cvmod doesn't exist or isn't a character, item, or useable.")
2019-05-30 15:44:29 -04:00
# behaviors
2019-06-08 13:42:00 -04:00
def stand(self, actor):
pass
def wander(self, actor):
pass
# stuff for extended classes to use
def registerUseFunc(self, name, func):
"""Registers a function for use by things in the map, but not directly
callable by the player."""
self.__useFuncs[name] = func
def registerBehavior(self, name, func):
"""Registers a function for use as an NPC's behavior."""
self.__behaviors[name] = func
def registerEvent(self, name, func):
"""Registers a function to handle an event in the event loop.
They should take the event as an argument, and return True if it should
always give the player a turn, False otherwise."""
self.__gameEvents[name] = func
def registerIO(self, name, func):
"""Registers a function for useFuncs and such to use."""
self.__IOCalls[name] = func
2019-05-30 15:44:29 -04:00
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:
raise GameError("No IO call for {}.".format(name))
return self.__IOCalls[name]