added "info" and "wear" use functions, and fixed many bugs related to storing things by ID.

This commit is contained in:
Patrick Marsee 2019-05-28 14:23:15 -04:00
parent 1b41c46105
commit 8355dccb33
4 changed files with 165 additions and 44 deletions

View file

@ -25,7 +25,7 @@ class GameBase(object):
self.__behaviors = {}
self.__gameEvents = {}
self.__IOCalls = {} # {str : function}
self.customVals = {} # for setting flags and such
self.customValues = {} # for setting flags and such
self.level = None
self.persist = {} # {level : {thingName : thing}}
self.ps2 = '? '
@ -58,6 +58,8 @@ class GameBase(object):
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
@ -138,16 +140,16 @@ Returns (thing, x, y). "Thing" can be None."""
elif thing != None:
return thing, thing.x, thing.y
elif allowInventory:
if name in self.playerInv:
thing = self.playerInv[name]
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))
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))
@ -295,6 +297,7 @@ 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)
@ -303,19 +306,21 @@ the name of an item in the player's inventory."""
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.name not in self.playerInv:
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.name in self.playerInv:
self.setEvent(0.125, _ge.UseEvent(thing))
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:
@ -335,7 +340,7 @@ the name of an item in the player's inventory."""
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))
self.setEvent(t * speed + 0.125, _ge.UseEvent(thing, useArgs))
return
def useOn(self, args, speed):
@ -345,7 +350,10 @@ the name of an item in the player's inventory."""
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:
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
@ -356,7 +364,7 @@ the name of an item in the player's inventory."""
# Similar to go, but not quite the same.
if (x, y) == (self.playerx, self.playery):
self.setEvent(0.125, _ge.UseOnEvent(item, thing))
self.setEvent(0.125, _ge.UseOnEvent(item, thing, useArgs))
return
dist, path = self.level.path(x, y, self.playerx, self.playery)
if dist == -1:
@ -379,7 +387,7 @@ the name of an item in the player's inventory."""
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))
self.setEvent(t * speed + 0.125, _ge.UseOnEvent(item, thing, useArgs))
return
def take(self, args):
@ -434,10 +442,12 @@ Object can be the name of the object, or its coordinates."""
"""drop [the] item"""
if args[0] == 'the':
args.pop(0)
if args[0] in self.playerInv:
for i in self.playerInv:
thingName = self.playerInv[i].name
if ' '.join(args) == thingName:
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)
return
print('{0} does not have a {1}.'.format(self.playerName, args[0]), file = self.outstream)
def loadMap(self, args):
# loadMap (fileName)
@ -602,26 +612,26 @@ Object can be the name of the object, or its coordinates."""
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())
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), _ge.NoOpEvent())
self.setEvent(self.__useFuncs[e.item.useOnFunc](e.item, e.thing, e.args), _ge.NoOpEvent())
return False
def handleTake(self, e):
self.level.removeThingByName(e.item.name)
self.playerInv[e.item.name] = e.item
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.name]
del self.playerInv[e.item.thingID]
return True
def handleBehave(self, e):
@ -629,7 +639,7 @@ Object can be the name of the object, or its coordinates."""
# default useFuncs: take a list of arguments, return the time the use took
def examine(self, thing):
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))
@ -661,13 +671,50 @@ Object can be the name of the object, or its coordinates."""
self.customVals[thing.customValues['set']] = 0
return 0.0
def container(self, thing):
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 key(self, item, thing):
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)

View file

@ -79,15 +79,17 @@ class ArriveEvent(GameEvent):
self.t = t
class UseEvent(GameEvent):
def __init__(self, thing):
def __init__(self, thing, args):
super(UseEvent, self).__init__('use')
self.thing = thing
self.args = args
class UseOnEvent(GameEvent):
def __init__(self, item, thing):
def __init__(self, item, thing, args):
super(UseOnEvent, self).__init__('useon')
self.thing = thing # thing can be a coordinate pair?
self.item = item
self.args = args
class TakeEvent(GameEvent):
def __init__(self, item):

View file

@ -409,6 +409,29 @@ class MapExit(Thing):
return cls(parts['name'], parts['location'][0], parts['location'][1],
parts['id'], parts['destination'], prefix, graphic)
class MapEntrance(Thing):
yaml_flag = u'!MapEntrance'
# no graphic - should not be drawn
def __init__(self, x: int, y: int, exitid: int):
if prefix:
description = "{0} {1}".format(prefix, name)
super(MapEntrance, self).__init__('a', name, x, y, description, 1)
self.exitid = exitid
@classmethod
def to_yaml(cls, representer, node):
# save usual things
ret = {'location': (node.x, node.y), 'id': node.exitid}
return representer.represent_mapping(cls.yaml_flag, ret)
@classmethod
def from_yaml(cls, constructor, node):
parts = CommentedMap()
constructor.construct_mapping(node, parts, True)
# set default values for optional arguments
return cls(parts['location'][0], parts['location'][1], parts['id'])
class MapError(RuntimeError):
pass
@ -575,7 +598,6 @@ Entering a map through stdin will be obsolete once testing is over."""
# generate matrix and graph first
mapMatrix, mapGraph, dimensions = GameMap.parseMatrix(mat)
playerStart = (-1, -1)
level = GameMap(infile, mapGraph, mapMatrix, dimensions)
# Now, load other info
@ -664,6 +686,7 @@ list of lists of tuples."""
level.wallColors.append('#7F3F0F')
# get things
hasKnownEntrance = False
if 'loadOnce' in info and not preLoaded:
for thing in info['loadOnce']:
#print(type(thing))
@ -672,8 +695,9 @@ list of lists of tuples."""
for thing in info['loadAlways']:
#print(type(thing))
nextThing = level.addThing(thing, nextThing)
if thing.thingType == 'x' and prevMap == thing.exitid:
if ((thing.thingType == 'x' and not hasKnownEntrance) or thing.thingType == 'a') and prevMap == thing.exitid:
level.playerStart = (thing.x, thing.y)
hasKnownEntrance = True
return nextThing
# stuff the gameshell itself might use
@ -686,6 +710,14 @@ list of lists of tuples."""
if thing.thingID == -1: # This is to ensure that we don't double up IDs.
thing.thingID = nextThing
nextThing += 1
# Some things, like containers, have other things as custom values,
# so they need IDs as well.
if thing.thingType in 'iuc':
nextThing = self.addThingRecursive(thing.customValues, nextThing)
if thing.thingType == 'c':
for i in thing.inventory:
i.thingID = nextThing
nextThing += 1
pos = self.coordsToInt(thing.x, thing.y)
if pos not in self.thingPos:
self.thingPos[pos] = [thing.thingID]
@ -700,6 +732,21 @@ list of lists of tuples."""
self.persistent.append(thing.thingID)
return nextThing
def addThingRecursive(self, container, nextThing = 0):
if isinstance(container, Thing):
container.thingID = nextThing
return nextThing + 1
elif isinstance(container, dict):
for i in container:
nextThing = self.addThingRecursive(container[i], nextThing)
return nextThing
elif isinstance(container, list):
for i in container:
nextThing = self.addThingRecursive(i, nextThing)
return nextThing
else:
return nextThing
def getThing(self, **kwargs):
if 'name' in kwargs:
return self.getThingByName(kwargs['name'])
@ -885,7 +932,7 @@ The closeEnough parameter will create a path that lands beside the source if nec
if len(self.thingPos[oldPos]) == 0:
del self.thingPos[oldPos]
oldName = thing.name
if oldName in self.thingPos:
if oldName in self.thingNames:
self.thingNames[oldName].remove(thing.thingID)
if len(self.thingNames[oldName]) == 0:
del self.thingNames[oldName]

View file

@ -56,6 +56,7 @@ class GameShell(Shell):
self.registerAlias('run', ['go', '-r'])
self.gameBase.registerIO('container', self.container)
self.gameBase.registerIO('dialog', self.dialog)
self.gameBase.registerIO('info', self.info)
# Helper functions
@ -212,6 +213,9 @@ If -l is given, a map legend will be printed under the map."""
ret.append("Player name:{0:.>68}".format(self.gameBase.playerName))
ret.append("Player position:{0:.>64}".format("{0}{1}".format(self.gameBase.numberToLetter(self.gameBase.playerx), self.gameBase.playery)))
ret.append("Prev. position:{0:.>65}".format("{0}{1}".format(self.gameBase.numberToLetter(self.gameBase.prevx), self.gameBase.prevy)))
ret.append("Inventory:")
for i in self.gameBase.playerInv:
ret.append("{0:<8}: {1}".format(i, self.gameBase.playerInv[i].name))
ret.append("Things:\nID Name X Y")
for i in self.gameBase.level.things:
j = self.gameBase.level.things[i]
@ -224,7 +228,7 @@ If -l is given, a map legend will be printed under the map."""
return
def inv(self, args):
print('\n'.join([i for i in self.gameBase.playerInv]))
print('\n'.join([self.gameBase.playerInv[i].name for i in self.gameBase.playerInv]))
def container(self, inv, cont):
"""container IO"""
@ -236,7 +240,7 @@ If -l is given, a map legend will be printed under the map."""
longestLen = len(i)
longestStr = i
if longestLen > 0:
print('{{0:<{0}}}{1}'.format(longestLen+2, "Container:").format("Inv:"))
print('{{0:<{0}}}{1}'.format(max(6, longestLen+2), "Container:").format("Inv:"))
i = 0
invKeys = tuple(inv.keys())
while i < len(invKeys) and i < len(cont):
@ -264,7 +268,7 @@ If -l is given, a map legend will be printed under the map."""
thing = ' '.join(instr[1:])
for i in range(len(cont)):
if thing == cont[i].name:
inv[cont[i].name] = cont[i]
inv[cont[i].thingID] = cont[i]
del cont[i]
timeSpent += 0.5
print("{0} taken.".format(thing))
@ -273,15 +277,36 @@ If -l is given, a map legend will be printed under the map."""
# store something in the container
if instr[1] == "the":
del instr[1]
thing = ' '.join(instr[1:])
if thing in inv:
cont.append(inv[thing])
del inv[thing]
thingName = ' '.join(instr[1:])
for i in inv:
thing = inv[i].name
if thing == thingName:
cont.append(inv[i])
del inv[i]
print("{0} stored.".format(thing))
timeSpent += 0.5
break # so that all things with the same name don't get stored
instr = input("Take, store, or exit: ")
return inv, cont, timeSpent
def info(self, items):
"""IO for collections of information"""
charsRead = 0
for i in items:
print(' ', i)
instr = input("Choose an item to view, or exit: ")
while instr != 'exit':
if instr in items:
print(_tw.fill(items[instr], width = TERM_SIZE))
charsRead += len(items[instr])
else:
print('{} not here.'.format(instr))
input('<strike RETURN>')
for i in items:
print(' ', i)
instr = input("Choose an item to view, or exit: ")
return charsRead / 27 # based on average 250 words per minute, and word length of 5.5 + 1 for space.
def dialog(self, dialogObj):
if 'opener' in dialogObj:
print(_tw.fill(dialogObj['opener'], width = TERM_SIZE))