2019-03-09 19:08:46 -05:00
|
|
|
# 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
|
2019-05-20 21:13:56 -04:00
|
|
|
import ruamel.yaml as _yaml
|
2019-06-08 13:42:00 -04:00
|
|
|
import textwrap as _tw
|
2019-05-20 21:13:56 -04:00
|
|
|
|
|
|
|
class GameError(RuntimeError):
|
|
|
|
pass
|
2019-03-09 19:08:46 -05:00
|
|
|
|
2019-06-08 13:42:00 -04:00
|
|
|
class _ScriptBreak(object):
|
|
|
|
|
|
|
|
def __init__(self, value):
|
|
|
|
self.value = value
|
|
|
|
|
2019-03-09 19:08:46 -05:00
|
|
|
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]+)\))')
|
2019-03-09 19:08:46 -05:00
|
|
|
#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 = {}
|
2019-05-20 21:13:56 -04:00
|
|
|
self.__behaviors = {}
|
2019-03-09 19:08:46 -05:00
|
|
|
self.__gameEvents = {}
|
2019-03-14 19:52:58 -04:00
|
|
|
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
|
2019-05-28 14:23:15 -04:00
|
|
|
self.customValues = {} # for setting flags and such
|
2019-03-09 19:08:46 -05:00
|
|
|
self.level = None
|
|
|
|
self.persist = {} # {level : {thingName : thing}}
|
|
|
|
self.ps2 = '? '
|
|
|
|
self.eventQueue = []
|
|
|
|
self.gameTime = 0.0
|
|
|
|
self.skipLoop = True
|
2019-05-15 01:37:35 -04:00
|
|
|
self.nextThing = 0
|
2019-03-09 19:08:46 -05:00
|
|
|
|
|
|
|
# player info
|
|
|
|
self.playerx = -1
|
|
|
|
self.playery = -1
|
|
|
|
self.prevx = -1 # prevx and prevy are for moving the player out of the way
|
|
|
|
self.prevy = -1 # when the tile they're standing on becomes blocked.
|
|
|
|
self.playerName = 'You'
|
|
|
|
self.playerInv = {}
|
|
|
|
|
|
|
|
# function deligates
|
|
|
|
self.onLevelLoad = None # str : level name? -> None
|
2019-03-14 19:52:58 -04:00
|
|
|
#self.onContainer = None # list : contents -> list : newContents, float : timeOpen
|
2019-03-09 19:08:46 -05:00
|
|
|
|
|
|
|
# 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)
|
2019-05-20 21:13:56 -04:00
|
|
|
self.registerEvent('behave', self.handleBehave)
|
2019-03-09 19:08:46 -05:00
|
|
|
self.registerUseFunc('examine', self.examine)
|
|
|
|
self.registerUseFunc('key', self.key)
|
2019-03-14 19:52:58 -04:00
|
|
|
self.registerUseFunc('container', self.container)
|
2019-05-28 14:23:15 -04:00
|
|
|
self.registerUseFunc('info', self.info)
|
|
|
|
self.registerUseFunc('wear', self.wear)
|
2019-06-08 13:42:00 -04:00
|
|
|
self.registerBehavior('stand', self.stand)
|
2019-05-20 21:13:56 -04:00
|
|
|
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)
|
2019-03-09 19:08:46 -05:00
|
|
|
|
|
|
|
# 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."""
|
|
|
|
#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:
|
|
|
|
for i in self.playerInv:
|
|
|
|
thingName = self.playerInv[i].name
|
|
|
|
if name == thingName:
|
|
|
|
return self.playerInv[i], -1, -1
|
|
|
|
|
|
|
|
# Third, try by location
|
|
|
|
coordStr = args[0]
|
|
|
|
if len(args) > 1:
|
|
|
|
coordStr += ' ' + args[1]
|
2019-03-09 19:08:46 -05:00
|
|
|
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
|
2019-03-09 19:08:46 -05:00
|
|
|
|
|
|
|
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':
|
|
|
|
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:
|
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-05-31 21:37:52 -04:00
|
|
|
|
2019-06-08 13:42:00 -04:00
|
|
|
def compareValues(self, args: list):
|
2019-05-31 21:37:52 -04:00
|
|
|
"""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':
|
|
|
|
for i in rval:
|
|
|
|
if rval[i].name == lval:
|
|
|
|
return True
|
|
|
|
return False
|
|
|
|
else:
|
|
|
|
return lval in rval
|
|
|
|
else:
|
|
|
|
raise GameError("Condition cannot be evaluated: {}".format(' '.join(args)))
|
2019-05-30 15:44:29 -04:00
|
|
|
|
|
|
|
# commands
|
|
|
|
|
2019-03-09 19:08:46 -05:00
|
|
|
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:
|
2019-05-20 21:13:56 -04:00
|
|
|
raise GameError("Cannot move: No level has been loaded.")
|
2019-03-09 19:08:46 -05:00
|
|
|
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.playerx, self.playery):
|
|
|
|
#_hq.heappush(self.eventQueue, (self.gameTime, _ge.ArriveEvent(self.playerName, x, y, 0.0)))
|
2019-06-08 13:42:00 -04:00
|
|
|
self.setEvent(0.0, _ge.ArriveEvent(self.playerName, self.playerx, self.playery, 0.0))
|
2019-03-09 19:08:46 -05:00
|
|
|
return
|
|
|
|
dist, path = self.level.path(x, y, self.playerx, self.playery)
|
|
|
|
if dist == -1:
|
|
|
|
print('{0} cannot reach {1}{2}.'.format(self.playerName, self.numberToLetter(x), y), file = self.outstream)
|
|
|
|
#_hq.heappush(self.eventQueue, (self.gameTime, _ge.NoOpEvent()))
|
|
|
|
return
|
|
|
|
else:
|
|
|
|
pos = self.level.coordsToInt(self.playerx, self.playery)
|
|
|
|
space = path[pos]
|
|
|
|
if space == -1:
|
|
|
|
self.setEvent(0.0, _ge.ArriveEvent(self.playerName, self.playerx, self.playery, 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.playerName, newx, newy))
|
|
|
|
else:
|
|
|
|
self.setEvent(t * speed, _ge.ArriveEvent(self.playerName, newx, newy, t * speed))
|
|
|
|
break
|
|
|
|
t += 1
|
|
|
|
#newx, newy = self.level.intToCoords(space)
|
|
|
|
#_hq.heappush(self.eventQueue, (self.gameTime + t * speed, _ge.ArriveEvent(self.playerName, 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 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)
|
2019-05-15 17:56:16 -04:00
|
|
|
if not self.level.lineOfSight(self.playerx, self.playery, x, y):
|
2019-05-20 21:13:56 -04:00
|
|
|
print("{} cannot see that.".format(self.playerName), file = self.outstream)
|
2019-05-15 17:56:16 -04:00
|
|
|
elif thing == None:
|
2019-03-09 19:08:46 -05:00
|
|
|
print("There is nothing to see here.\n", file = self.outstream)
|
2019-05-15 17:56:16 -04:00
|
|
|
else:
|
|
|
|
print(self.justifyText(str(thing)), file = self.outstream)
|
2019-03-09 19:08:46 -05:00
|
|
|
#_hq.heappush(self.eventQueue, (self.gameTime, _ge.NoOpEvent()))
|
|
|
|
|
2019-05-20 21:13:56 -04:00
|
|
|
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 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)
|
2019-05-20 21:13:56 -04:00
|
|
|
if not self.level.lineOfSight(self.playerx, self.playery, x, y):
|
|
|
|
print("{} cannot talk to {}.".format(self.playerName, 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)
|
|
|
|
|
2019-03-09 19:08:46 -05:00
|
|
|
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."""
|
|
|
|
speed = 0.6666667
|
2019-05-28 14:23:15 -04:00
|
|
|
useArgs = []
|
2019-03-09 19:08:46 -05:00
|
|
|
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
|
2019-05-28 14:23:15 -04:00
|
|
|
if 'with' in args:
|
|
|
|
useArgs = args[args.index('with')+1:]
|
2019-03-09 19:08:46 -05:00
|
|
|
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
|
2019-05-28 14:23:15 -04:00
|
|
|
if thing.thingType != 'u' and thing.thingID not in self.playerInv:
|
2019-03-09 19:08:46 -05:00
|
|
|
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.
|
2019-05-28 14:23:15 -04:00
|
|
|
if (x, y) == (self.playerx, self.playery) or thing.thingID in self.playerInv:
|
|
|
|
self.setEvent(0.125, _ge.UseEvent(thing, useArgs))
|
2019-03-09 19:08:46 -05:00
|
|
|
return
|
|
|
|
dist, path = self.level.path(x, y, self.playerx, self.playery)
|
|
|
|
if dist == -1:
|
|
|
|
print('{0} cannot reach the {1}.'.format(self.playerName, thing.name), file = self.outstream)
|
|
|
|
#_hq.heappush(self.eventQueue, (self.gameTime, _ge.NoOpEvent()))
|
|
|
|
return
|
|
|
|
else:
|
|
|
|
pos = self.level.coordsToInt(self.playerx, self.playery)
|
|
|
|
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.playerName, newx, newy))
|
|
|
|
t += 1
|
|
|
|
#newx, newy = self.level.intToCoords(space)
|
|
|
|
#_hq.heappush(self.eventQueue, (self.gameTime + t * speed, _ge.GoEvent(self.playerName, newx, newy)))
|
2019-05-28 14:23:15 -04:00
|
|
|
self.setEvent(t * speed + 0.125, _ge.UseEvent(thing, useArgs))
|
2019-03-09 19:08:46 -05:00
|
|
|
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)
|
2019-03-09 19:08:46 -05:00
|
|
|
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)
|
2019-05-28 14:23:15 -04:00
|
|
|
useArgs = []
|
|
|
|
if 'with' in args:
|
|
|
|
useArgs = args[args.index('with')+1:]
|
|
|
|
if item == None or item.thingID not in self.playerInv:
|
2019-06-08 13:42:00 -04:00
|
|
|
print("There is no such item in the inventory.", file = self.outstream)
|
2019-03-09 19:08:46 -05:00
|
|
|
#_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
|
|
|
|
|
|
|
|
# Similar to go, but not quite the same.
|
|
|
|
if (x, y) == (self.playerx, self.playery):
|
2019-05-28 14:23:15 -04:00
|
|
|
self.setEvent(0.125, _ge.UseOnEvent(item, thing, useArgs))
|
2019-03-09 19:08:46 -05:00
|
|
|
return
|
|
|
|
dist, path = self.level.path(x, y, self.playerx, self.playery)
|
|
|
|
if dist == -1:
|
|
|
|
if thing != None:
|
|
|
|
print('{0} cannot reach the {1}.'.format(self.playerName, thing.name), file = self.outstream)
|
|
|
|
else:
|
|
|
|
print('{0} cannot reach {1}{2}.'.format(self.playerName, self.numberToLetter(x), y), file = self.outstream)
|
|
|
|
#_hq.heappush(self.eventQueue, (self.gameTime, _ge.NoOpEvent()))
|
|
|
|
return
|
|
|
|
else:
|
|
|
|
pos = self.level.coordsToInt(self.playerx, self.playery)
|
|
|
|
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.playerName, newx, newy))
|
|
|
|
t += 1
|
|
|
|
#newx, newy = self.level.intToCoords(space)
|
|
|
|
#_hq.heappush(self.eventQueue, (self.gameTime + t * speed, _ge.GoEvent(self.playerName, newx, newy)))
|
2019-05-28 14:23:15 -04:00
|
|
|
self.setEvent(t * speed + 0.125, _ge.UseOnEvent(item, thing, useArgs))
|
2019-03-09 19:08:46 -05:00
|
|
|
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."""
|
|
|
|
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)
|
2019-03-09 19:08:46 -05:00
|
|
|
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.playerx, self.playery):
|
|
|
|
self.setEvent(0.125, _ge.TakeEvent(thing))
|
|
|
|
return
|
|
|
|
dist, path = self.level.path(x, y, self.playerx, self.playery)
|
|
|
|
if dist == -1:
|
|
|
|
print('{0} cannot reach the {1}.'.format(self.playerName, thing.name), file = self.outstream)
|
|
|
|
#_hq.heappush(self.eventQueue, (self.gameTime, _ge.NoOpEvent()))
|
|
|
|
return
|
|
|
|
else:
|
|
|
|
pos = self.level.coordsToInt(self.playerx, self.playery)
|
|
|
|
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.playerName, 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.TakeEvent(thing))
|
|
|
|
return
|
|
|
|
|
|
|
|
def drop(self, args):
|
|
|
|
"""drop [the] item"""
|
|
|
|
if args[0] == 'the':
|
|
|
|
args.pop(0)
|
2019-05-28 14:23:15 -04:00
|
|
|
for i in self.playerInv:
|
|
|
|
thingName = self.playerInv[i].name
|
|
|
|
if ' '.join(args) == thingName:
|
2019-06-08 13:42:00 -04:00
|
|
|
self.setEvent(0.0, _ge.DropEvent(self.playerInv[i]))
|
2019-05-28 14:23:15 -04:00
|
|
|
return
|
|
|
|
print('{0} does not have a {1}.'.format(self.playerName, args[0]), file = self.outstream)
|
2019-03-09 19:08:46 -05:00
|
|
|
|
|
|
|
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:
|
2019-05-24 14:06:34 -04:00
|
|
|
self.persist[self.level.name][i] = self.level.getThingByID(i)
|
2019-03-09 19:08:46 -05:00
|
|
|
|
|
|
|
preLoaded = False
|
|
|
|
if args[0] in self.persist:
|
|
|
|
preLoaded = True
|
|
|
|
|
|
|
|
# load the new level
|
|
|
|
if len(args) == 2:
|
2019-05-15 01:37:35 -04:00
|
|
|
self.level, self.nextThing = _gm.GameMap.read(args[0], int(args[1]), preLoaded, self.nextThing)
|
2019-03-09 19:08:46 -05:00
|
|
|
else:
|
2019-05-15 01:37:35 -04:00
|
|
|
self.level, self.nextThing = _gm.GameMap.read(args[0], None, preLoaded, self.nextThing)
|
2019-03-09 19:08:46 -05:00
|
|
|
|
2019-06-08 13:42:00 -04:00
|
|
|
if self.level == None:
|
|
|
|
raise GameError("Map could not be loaded.")
|
|
|
|
|
2019-03-09 19:08:46 -05:00
|
|
|
# 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
|
2019-03-09 19:08:46 -05:00
|
|
|
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())
|
|
|
|
if len(args) <= 2:
|
|
|
|
self.playerx, self.playery = self.level.playerStart
|
|
|
|
else:
|
|
|
|
self.playerx, self.playery = int(args[1]), int(args[2])
|
|
|
|
self.prevx, self.prevy = self.playerx, self.playery
|
|
|
|
if self.onLevelLoad != None:
|
|
|
|
self.onLevelLoad()
|
2019-05-30 15:44:29 -04:00
|
|
|
self.parseScript(self.level.enterScript)
|
2019-03-09 19:08:46 -05:00
|
|
|
|
|
|
|
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)
|
2019-03-09 19:08:46 -05:00
|
|
|
|
|
|
|
# build data object to be saved
|
2019-06-08 13:42:00 -04:00
|
|
|
data = (self.playerName, self.playerx, self.playery, self.playerInv, self.level.name, self.persist, self.eventQueue, self.customValues, self.gameTime, self.nextThing)
|
2019-03-09 19:08:46 -05:00
|
|
|
|
|
|
|
# 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:
|
2019-06-08 13:42:00 -04:00
|
|
|
self.playerName, x, y, self.playerInv, levelname, self.persist, self.eventQueue, self.customValues, self.gameTime, self.nextThing = _pi.load(f)
|
2019-03-09 19:08:46 -05:00
|
|
|
#print(levelname, x, y, file = self.outstream)
|
|
|
|
self.loadMap((levelname, x, y))
|
|
|
|
#_hq.heappush(self.eventQueue, (self.gameTime, _ge.NoOpEvent()))
|
|
|
|
return
|
|
|
|
|
2019-05-30 15:44:29 -04:00
|
|
|
def dialog(self, dialogName, conversant = None):
|
2019-05-20 21:13:56 -04:00
|
|
|
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
|
|
|
|
2019-03-09 19:08:46 -05: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:
|
2019-06-08 13:42:00 -04:00
|
|
|
actor = self.level.getThingByName(e.actor)
|
|
|
|
self.level.moveThing(actor, e.x, e.y)
|
2019-03-09 19:08:46 -05:00
|
|
|
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':
|
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))
|
2019-03-09 19:08:46 -05:00
|
|
|
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
|
|
|
|
|
|
|
|
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
|
2019-05-28 14:23:15 -04:00
|
|
|
self.setEvent(self.__useFuncs[e.thing.useFunc](e.thing, e.args), _ge.NoOpEvent())
|
2019-03-09 19:08:46 -05:00
|
|
|
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
|
2019-05-28 14:23:15 -04:00
|
|
|
self.setEvent(self.__useFuncs[e.item.useOnFunc](e.item, e.thing, e.args), _ge.NoOpEvent())
|
2019-03-09 19:08:46 -05:00
|
|
|
return False
|
|
|
|
|
|
|
|
def handleTake(self, e):
|
2019-05-28 14:23:15 -04:00
|
|
|
self.level.removeThingByID(e.item.thingID)
|
|
|
|
self.playerInv[e.item.thingID] = e.item
|
2019-03-09 19:08:46 -05:00
|
|
|
return True
|
|
|
|
|
|
|
|
def handleDrop(self, e):
|
|
|
|
e.item.x = self.playerx
|
|
|
|
e.item.y = self.playery
|
2019-06-08 13:42:00 -04:00
|
|
|
self.nextThing = self.level.addThing(e.item, self.nextThing, True) # nextThing shouldn't change
|
2019-05-28 14:23:15 -04:00
|
|
|
del self.playerInv[e.item.thingID]
|
2019-03-09 19:08:46 -05:00
|
|
|
return True
|
2019-05-20 21:13:56 -04:00
|
|
|
|
|
|
|
def handleBehave(self, e):
|
|
|
|
self.__behaviors[e.actor.behavior](e.actor)
|
2019-03-09 19:08:46 -05:00
|
|
|
|
|
|
|
# default useFuncs: take a list of arguments, return the time the use took
|
|
|
|
|
2019-05-28 14:23:15 -04:00
|
|
|
def examine(self, thing, args):
|
2019-03-09 19:08:46 -05:00
|
|
|
"""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)
|
2019-03-09 19:08:46 -05:00
|
|
|
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)
|
2019-03-09 19:08:46 -05:00
|
|
|
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)
|
2019-03-09 19:08:46 -05:00
|
|
|
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)
|
2019-03-09 19:08:46 -05:00
|
|
|
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]
|
2019-03-09 19:08:46 -05:00
|
|
|
else:
|
2019-06-08 13:42:00 -04:00
|
|
|
self.customValues[thing.customValues['set']] = True
|
2019-03-09 19:08:46 -05:00
|
|
|
return 0.0
|
|
|
|
|
2019-05-28 14:23:15 -04:00
|
|
|
def container(self, thing, args):
|
2019-03-09 19:08:46 -05:00
|
|
|
"""Acts as a container. Items can be traded between the container and the player's inventory."""
|
2019-03-14 19:52:58 -04:00
|
|
|
items = list(thing.customValues['items'])
|
|
|
|
self.playerInv, thing.customValues['items'], timeOpen = self.getIO('container')(self.playerInv, items)
|
2019-03-09 19:08:46 -05:00
|
|
|
return timeOpen
|
|
|
|
|
2019-05-28 14:23:15 -04:00
|
|
|
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 = False
|
|
|
|
for i in self.playerInv:
|
|
|
|
if i == item.thingID:
|
|
|
|
inInv = True
|
|
|
|
break
|
|
|
|
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']] = item
|
|
|
|
# This is so a player can't put on a garment, then drop it while
|
|
|
|
# still also wearing it.
|
|
|
|
del self.playerInv[item.thingID]
|
|
|
|
print("{} put on the {}.".format(self.playerName, 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
|
|
|
|
oldItem = self.customValues['wearing'][item.customValues['slot']]
|
|
|
|
self.playerInv[oldItem.thingID] = oldItem
|
|
|
|
self.customValues['wearing'][item.customValues['slot']] = item
|
|
|
|
# This is so a player can't put on a garment, then drop it while
|
|
|
|
# still also wearing it.
|
|
|
|
del self.playerInv[item.thingID]
|
|
|
|
print("{} took off the {} and put on the {}.".format(self.playerName, oldItem.name, item.name), file = self.outstream)
|
|
|
|
return 1.0
|
|
|
|
|
|
|
|
# item use-on functions
|
|
|
|
|
|
|
|
def key(self, item, thing, args):
|
2019-03-09 19:08:46 -05:00
|
|
|
"""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.playerx == thing.x and self.playery == thing.y:
|
|
|
|
self.playerx, self.playery = self.prevx, self.prevy
|
|
|
|
else:
|
|
|
|
print("The key doesn't fit that lock.", file = self.outstream)
|
|
|
|
return 0.125
|
2019-05-20 21:13:56 -04:00
|
|
|
|
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:])
|
2019-05-30 15:44:29 -04:00
|
|
|
thing = _gm.NPC(name, x, y, description, behavior, inv, customValues, playerx, playery, False, (bgc, fgc, shape))
|
|
|
|
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.playerInv[thing.thingID] = thing
|
|
|
|
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
|
|
|
|
2019-05-20 21:13:56 -04:00
|
|
|
# behaviors
|
|
|
|
|
2019-06-08 13:42:00 -04:00
|
|
|
def stand(self, actor):
|
|
|
|
pass
|
|
|
|
|
2019-05-20 21:13:56 -04:00
|
|
|
def wander(self, actor):
|
|
|
|
pass
|
2019-03-09 19:08:46 -05:00
|
|
|
|
|
|
|
# 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
|
2019-05-20 21:13:56 -04:00
|
|
|
|
|
|
|
def registerBehavior(self, name, func):
|
|
|
|
"""Registers a function for use as an NPC's behavior."""
|
|
|
|
self.__behaviors[name] = func
|
2019-03-09 19:08:46 -05:00
|
|
|
|
|
|
|
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
|
2019-03-14 19:52:58 -04:00
|
|
|
|
|
|
|
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
|
|
|
|
|
2019-03-14 19:52:58 -04:00
|
|
|
def getIO(self, name):
|
|
|
|
"""This is so derived classes can access their IOCalls."""
|
2019-05-20 21:13:56 -04:00
|
|
|
if name not in self.__IOCalls:
|
|
|
|
raise GameError("No IO call for {}.".format(name))
|
2019-03-14 19:52:58 -04:00
|
|
|
return self.__IOCalls[name]
|