638 lines
27 KiB
Python
638 lines
27 KiB
Python
# 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
|
|
|
|
class GameBase(object):
|
|
|
|
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.__gameEvents = {}
|
|
self.customVals = {} # for setting flags and such
|
|
self.level = None
|
|
self.persist = {} # {level : {thingName : thing}}
|
|
self.ps2 = '? '
|
|
self.eventQueue = []
|
|
self.gameTime = 0.0
|
|
self.skipLoop = True
|
|
|
|
# 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
|
|
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.registerUseFunc('examine', self.examine)
|
|
self.registerUseFunc('key', self.key)
|
|
|
|
# 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."""
|
|
coordStr = args[0]
|
|
if len(args) > 1:
|
|
coordStr += ' ' + args[1]
|
|
#print(coordStr)
|
|
x = -1
|
|
y = -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
|
|
name = ' '.join(args)
|
|
thing = self.level.getThingByName(name)
|
|
if thing != None and usePlayerCoords:
|
|
return thing, thing.playerx, thing.playery
|
|
elif thing != None:
|
|
return thing, thing.x, thing.y
|
|
elif allowInventory:
|
|
if name in self.playerInv:
|
|
thing = self.playerInv[name]
|
|
if thing != None:
|
|
return thing, -1, -1
|
|
else:
|
|
return None, -1, -1
|
|
#raise RuntimeError("'None' item named '{0}' in player inventory.".format(name))
|
|
else:
|
|
return None, -1, -1
|
|
#raise ValueError("{0} cannot be reached.".format(name))
|
|
else: # nothing
|
|
return None, -1, -1
|
|
#raise ValueError("{0} cannot be reached.".format(name))
|
|
return None, x, y
|
|
|
|
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)
|
|
|
|
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 RuntimeError("Cannot move: No level has been loaded.")
|
|
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)))
|
|
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)
|
|
if thing:
|
|
print(self.justifyText(str(thing)), file = self.outstream)
|
|
else:
|
|
print("There is nothing to see here.\n", file = self.outstream)
|
|
#_hq.heappush(self.eventQueue, (self.gameTime, _ge.NoOpEvent()))
|
|
|
|
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
|
|
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
|
|
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.name not in self.playerInv:
|
|
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.playerx, self.playery) or thing.name in self.playerInv:
|
|
self.setEvent(0.125, _ge.UseEvent(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.UseEvent(thing))
|
|
return
|
|
|
|
def useOn(self, args, speed):
|
|
"""Called by use when there is an 'on' clause"""
|
|
onIndex = args.index('on')
|
|
item, x, y = self.parseCoords(args[:onIndex])
|
|
if args[onIndex+1] == 'the':
|
|
onIndex += 1
|
|
thing, x, y = self.parseCoords(args[onIndex+1:])
|
|
if item == None or item.name not in self.playerInv:
|
|
print("There is no {0} in the inventory.".format(item.name), 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
|
|
|
|
# Similar to go, but not quite the same.
|
|
if (x, y) == (self.playerx, self.playery):
|
|
self.setEvent(0.125, _ge.UseOnEvent(item, thing))
|
|
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)))
|
|
self.setEvent(t * speed + 0.125, _ge.UseOnEvent(item, thing))
|
|
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)
|
|
thing, x, y = self.parseCoords(args)
|
|
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)
|
|
if args[0] in self.playerInv:
|
|
self.setEvent(0.0, _ge.DropEvent(self.playerInv[args[0]]))
|
|
else:
|
|
print('{0} do not have a {1}.'.format(self.playerName, 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.getThingByName(i)
|
|
|
|
preLoaded = False
|
|
if args[0] in self.persist:
|
|
preLoaded = True
|
|
|
|
# load the new level
|
|
if len(args) == 2:
|
|
self.level = _gm.GameMap.read(args[0], int(args[1]), preLoaded)
|
|
else:
|
|
self.level = _gm.GameMap.read(args[0], preLoaded)
|
|
|
|
# get persistent things from it
|
|
if args[0] in self.persist:
|
|
persistedThings = tuple(self.persist[args[0]].keys())
|
|
for i in persistedThings:
|
|
self.level.addThing(self.persist[args[0]][i], True)
|
|
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()
|
|
|
|
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:
|
|
self.persist[self.level.name][i] = self.level.getThingByName(i)
|
|
|
|
# build data object to be saved
|
|
data = (self.playerName, self.playerx, self.playery, self.playerInv, self.level.name, self.persist, self.eventQueue, self.gameTime)
|
|
|
|
# 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.playerName, x, y, self.playerInv, levelname, self.persist, self.eventQueue, self.gameTime = _pi.load(f)
|
|
#print(levelname, x, y, file = self.outstream)
|
|
self.loadMap((levelname, x, y))
|
|
#_hq.heappush(self.eventQueue, (self.gameTime, _ge.NoOpEvent()))
|
|
return
|
|
|
|
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:
|
|
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':
|
|
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
|
|
|
|
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), _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), _ge.NoOpEvent())
|
|
return False
|
|
|
|
def handleTake(self, e):
|
|
self.level.removeThing(e.item.name)
|
|
self.playerInv[e.item.name] = e.item
|
|
return True
|
|
|
|
def handleDrop(self, e):
|
|
e.item.x = self.playerx
|
|
e.item.y = self.playery
|
|
self.level.addThing(e.item, True)
|
|
del self.playerInv[e.item.name]
|
|
return True
|
|
|
|
# default useFuncs: take a list of arguments, return the time the use took
|
|
|
|
def examine(self, thing):
|
|
"""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':
|
|
print(self.justifyText(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']
|
|
print(self.justifyText(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']
|
|
print(self.justifyText(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']))
|
|
print(self.justifyText(thing.customValues['text'][cursor]), file = self.outstream)
|
|
thing.customValues['cursor'] = cursor
|
|
|
|
if 'set' in thing.customValues:
|
|
if 'cursor' in thing.customValues:
|
|
self.customVals[thing.customValues['set']] = thing.customValues[cursor]
|
|
else:
|
|
self.customVals[thing.customValues['set']] = 0
|
|
return 0.0
|
|
|
|
def container(self, thing):
|
|
"""Acts as a container. Items can be traded between the container and the player's inventory."""
|
|
items = thing.customValues['items']
|
|
thing.customValues['items'], timeOpen = self.onContainer(items)
|
|
return timeOpen
|
|
|
|
def key(self, item, thing):
|
|
"""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
|
|
|
|
# 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 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
|