gameshell/gamebase.py

759 lines
33 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
import ruamel.yaml as _yaml
class GameError(RuntimeError):
pass
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.__behaviors = {}
self.__gameEvents = {}
self.__IOCalls = {} # {str : function}
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.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.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)
self.registerBehavior('wander', self.wander)
# 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:
for i in self.playerInv:
thingName = self.playerInv[i].name
if name == thingName:
thing = self.playerInv[i]
if thing != None:
return thing, -1, -1
else:
return None, -1, -1
#raise RuntimeError("'None' item named '{0}' in player inventory.".format(name))
return None, -1, -1
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 GameError("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 not self.level.lineOfSight(self.playerx, self.playery, x, y):
print("{} cannot see that.".format(self.playerName), 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 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)
thing, x, y = self.parseCoords(args, usePlayerCoords = False)
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)
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
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.thingID 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.thingID in self.playerInv:
self.setEvent(0.125, _ge.UseEvent(thing, useArgs))
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, useArgs))
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:])
useArgs = []
if 'with' in args:
useArgs = args[args.index('with')+1:]
if item == None or item.thingID 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, useArgs))
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, 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."""
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)
for i in self.playerInv:
thingName = self.playerInv[i].name
if ' '.join(args) == thingName:
self.setEvent(0.0, _ge.DropEvent(self.playerInv[args[0]]))
return
print('{0} does 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.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)
# 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, 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.playerName, x, y, self.playerInv, levelname, self.persist, self.eventQueue, self.gameTime, self.nextThing = _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 dialog(self, dialogName, conversant):
yaml = _yaml.YAML()
dialog = None
with open(dialogName, 'r') as f:
dialog = yaml.load(f)
self.getIO('dialog')(dialog)
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, 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):
self.level.removeThingByID(e.item.thingID)
self.playerInv[e.item.thingID] = 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.thingID]
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':
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, args):
"""Acts as a container. Items can be traded between the container and the player's inventory."""
items = list(thing.customValues['items'])
self.playerInv, thing.customValues['items'], timeOpen = self.getIO('container')(self.playerInv, 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 = 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):
"""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
# behaviors
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
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]