Rejiggered players to have a thing in the map, and made the inventory system better.

This commit is contained in:
Patrick Marsee 2019-07-04 18:48:47 -04:00
parent ed7d265b48
commit 5010042430
4 changed files with 347 additions and 287 deletions

View file

@ -42,12 +42,9 @@ class GameBase(object):
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 = {}
self.playerDescription = 'The main character.'
self.player = None # reference to the player's 'thing'
# function deligates
self.onLevelLoad = None # str : level name? -> None
@ -125,6 +122,8 @@ to get input from stdin."""
def parseCoords(self, args, usePlayerCoords = True, allowInventory = True):
"""Takes an argument list, and figures out what it's talking about.
Returns (thing, x, y). "Thing" can be None."""
if self.level == None:
raise GameError('Coordinates cannot be parsed because there is no map.')
#print(coordStr)
x = -1
y = -1
@ -140,10 +139,8 @@ Returns (thing, x, y). "Thing" can be None."""
# 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
if name in self.player.thingNames:
return self.player.getThingByName(name), -1, -1
# Third, try by location
coordStr = args[0]
@ -208,14 +205,16 @@ Returns (thing, x, y). "Thing" can be None."""
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 arg.casefold() == 'playerx' and self.player != None:
val = self.player.x
elif arg.casefold() == 'playery' and self.player != None:
val = self.player.y
elif arg.casefold() == 'playername' and self.player != None:
val = self.player.name
elif arg.casefold() == 'playerinv' and self.player != None:
val = self.player.inventory
elif arg.casefold() == 'playerdesc' and self.player != None:
val = self.player.description
elif validIdent != None:
group = validIdent.group()
if 'scriptLocal' in self.customValues and group in self.customValues['scriptLocal']:
@ -274,10 +273,7 @@ Returns (thing, x, y). "Thing" can be None."""
return lval > rval
elif operator == 'in':
if args[2].casefold() == 'playerinv':
for i in rval:
if rval[i].name == lval:
return True
return False
return lval in self.player.thingNames
else:
return lval in rval
else:
@ -298,6 +294,8 @@ wanted to go to D6, one could say "go to D6", "go to d 6", or "go to 3 6".
The letter is not case-sensitive."""
if self.level == None:
raise GameError("Cannot move: No level has been loaded.")
if self.player == None:
raise GameError("Cannot move: Player character doesn't exist.")
speed = 0.6666667
if args[0] == '-r' or args[0] == 'r' or args[0] == 'run':
speed = 0.3333333
@ -309,20 +307,20 @@ The letter is not case-sensitive."""
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):
if (x, y) == (self.player.x, self.player.y):
#_hq.heappush(self.eventQueue, (self.gameTime, _ge.ArriveEvent(self.playerName, x, y, 0.0)))
self.setEvent(0.0, _ge.ArriveEvent(self.playerName, self.playerx, self.playery, 0.0))
self.setEvent(0.0, _ge.ArriveEvent(self.player, self.player.x, self.player.y, 0.0))
return
dist, path = self.level.path(x, y, self.playerx, self.playery)
dist, path = self.level.path(x, y, self.player.x, self.player.y)
if dist == -1:
print('{0} cannot reach {1}{2}.'.format(self.playerName, self.numberToLetter(x), y), file = self.outstream)
print('{0} cannot reach {1}{2}.'.format(self.player.name, self.numberToLetter(x), y), file = self.outstream)
#_hq.heappush(self.eventQueue, (self.gameTime, _ge.NoOpEvent()))
return
else:
pos = self.level.coordsToInt(self.playerx, self.playery)
pos = self.level.coordsToInt(self.player.x, self.player.y)
space = path[pos]
if space == -1:
self.setEvent(0.0, _ge.ArriveEvent(self.playerName, self.playerx, self.playery, 0.0))
self.setEvent(0.0, _ge.ArriveEvent(self.player, self.player.x, self.player.y, 0.0))
return
#target = self.level.coordsToInt(x, y)
t = 1
@ -331,13 +329,13 @@ The letter is not case-sensitive."""
newx, newy = self.level.intToCoords(space)
space = path[space]
if space != -1:
self.setEvent(t * speed, _ge.GoEvent(self.playerName, newx, newy))
self.setEvent(t * speed, _ge.GoEvent(self.player, newx, newy))
else:
self.setEvent(t * speed, _ge.ArriveEvent(self.playerName, newx, newy, t * speed))
self.setEvent(t * speed, _ge.ArriveEvent(self.player, newx, newy, t * speed))
break
t += 1
#newx, newy = self.level.intToCoords(space)
#_hq.heappush(self.eventQueue, (self.gameTime + t * speed, _ge.ArriveEvent(self.playerName, newx, newy, t * speed)))
#_hq.heappush(self.eventQueue, (self.gameTime + t * speed, _ge.ArriveEvent(self.player, newx, newy, t * speed)))
return
def look(self, args):
@ -347,6 +345,10 @@ Describe an object.
make more sense from a linguistic perspective (for instance, one could
say "look at the balcony" rather than just "look balcony").
Object can be the name of the object, or its coordinates."""
if self.level == None:
raise GameError("Cannot look: No level has been loaded.")
if self.player == None:
raise GameError("Cannot look: Player character doesn't exist.")
if len(args) == 0:
print(self.justifyText(self.level.description), file = self.outstream)
else:
@ -355,8 +357,8 @@ Object can be the name of the object, or its coordinates."""
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)
if not self.level.lineOfSight(self.player.x, self.player.y, x, y):
print("{} cannot see that.".format(self.player.name), file = self.outstream)
elif thing == None:
print("There is nothing to see here.\n", file = self.outstream)
else:
@ -370,6 +372,10 @@ Talk to a character.
make more sense from a linguistic perspective (for instance, one could
say "talk to the guard" rather than just "talk guard").
Character can be the name of the character, or their coordinates."""
if self.level == None:
raise GameError("Cannot talk: No level has been loaded.")
if self.player == None:
raise GameError("Cannot talk: Player character doesn't exist.")
if len(args) == 0:
print(self.justifyText(self.level.description), file = self.outstream)
else:
@ -378,8 +384,8 @@ Character can be the name of the character, or their coordinates."""
if args[0] == 'the':
args.pop(0)
thing, x, y = self.parseCoords(args, usePlayerCoords = False, allowInventory = False)
if not self.level.lineOfSight(self.playerx, self.playery, x, y):
print("{} cannot talk to {}.".format(self.playerName, thing.name), file = self.outstream)
if not self.level.lineOfSight(self.player.x, self.player.y, x, y):
print("{} cannot talk to {}.".format(self.player.name, thing.name), file = self.outstream)
elif thing == None:
print("There is nobody here.\n", file = self.outstream)
else:
@ -407,6 +413,10 @@ make more sense from a linguistic perspective (for instance, one could
say "use the lever" rather than just "use lever").
Object can be the name of the object, or its coordinates. It can also be
the name of an item in the player's inventory."""
if self.level == None:
raise GameError("Cannot use: No level has been loaded.")
if self.player == None:
raise GameError("Cannot use: Player character doesn't exist.")
speed = 0.6666667
useArgs = []
if args[0] == '-r' or args[0] == 'r' or args[0] == 'run':
@ -424,22 +434,22 @@ the name of an item in the player's inventory."""
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:
if thing.thingType != 'u' and thing not in self.player.inventory:
print("The {0} cannot be used.".format(thing.name), file = self.outstream)
#_hq.heappush(self.eventQueue, (self.gameTime, _ge.NoOpEvent()))
return
# Similar to go, but not quite the same.
if (x, y) == (self.playerx, self.playery) or thing.thingID in self.playerInv:
if (x, y) == (self.player.x, self.player.y) or thing in self.player.inventory:
self.setEvent(0.125, _ge.UseEvent(thing, useArgs))
return
dist, path = self.level.path(x, y, self.playerx, self.playery)
dist, path = self.level.path(x, y, self.player.x, self.player.y)
if dist == -1:
print('{0} cannot reach the {1}.'.format(self.playerName, thing.name), file = self.outstream)
print('{0} cannot reach the {1}.'.format(self.player.name, thing.name), file = self.outstream)
#_hq.heappush(self.eventQueue, (self.gameTime, _ge.NoOpEvent()))
return
else:
pos = self.level.coordsToInt(self.playerx, self.playery)
pos = self.level.coordsToInt(self.player.x, self.player.y)
space = path[pos]
#target = self.level.coordsToInt(x, y)
t = 1
@ -447,7 +457,7 @@ the name of an item in the player's inventory."""
while space != -1:
newx, newy = self.level.intToCoords(space)
space = path[space]
self.setEvent(t * speed, _ge.GoEvent(self.playerName, newx, newy))
self.setEvent(t * speed, _ge.GoEvent(self.player, newx, newy))
t += 1
#newx, newy = self.level.intToCoords(space)
#_hq.heappush(self.eventQueue, (self.gameTime + t * speed, _ge.GoEvent(self.playerName, newx, newy)))
@ -464,7 +474,7 @@ the name of an item in the player's inventory."""
useArgs = []
if 'with' in args:
useArgs = args[args.index('with')+1:]
if item == None or item.thingID not in self.playerInv:
if item == None or item not in self.player.inventory:
print("There is no such item in the inventory.", file = self.outstream)
#_hq.heappush(self.eventQueue, (self.gameTime, _ge.NoOpEvent()))
return
@ -472,33 +482,41 @@ the name of an item in the player's inventory."""
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)
if not item.ranged:
# Similar to go, but not quite the same.
if (x, y) == (self.player.x, self.player.y):
self.setEvent(0.125, _ge.UseOnEvent(item, thing, useArgs))
return
dist, path = self.level.path(x, y, self.player.x, self.player.y)
if dist == -1:
if thing != None:
print('{0} cannot reach the {1}.'.format(self.player.name, thing.name), file = self.outstream)
else:
print('{0} cannot reach {1}{2}.'.format(self.player.name, self.numberToLetter(x), y), file = self.outstream)
#_hq.heappush(self.eventQueue, (self.gameTime, _ge.NoOpEvent()))
return
else:
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
pos = self.level.coordsToInt(self.player.x, self.player.y)
space = path[pos]
#target = self.level.coordsToInt(x, y)
t = 1
#while space != target:
while space != -1:
newx, newy = self.level.intToCoords(space)
space = path[space]
self.setEvent(t * speed, _ge.GoEvent(self.player, newx, newy))
t += 1
#newx, newy = self.level.intToCoords(space)
#_hq.heappush(self.eventQueue, (self.gameTime + t * speed, _ge.GoEvent(self.playerName, newx, newy)))
self.setEvent(t * speed + 0.125, _ge.UseOnEvent(item, thing, useArgs))
else:
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))
if not self.level.lineOfSight(self.player.x, self.player.y, x, y):
print("{} cannot use the {} on that from here.".format(self.player.name, item.name), file = self.outstream)
elif thing == None:
print("There is nothing to use the {} on here.".format(item.name), file = self.outstream)
else:
self.setEvent(0, _ge.UseOnEvent(item, thing, useArgs))
return
def take(self, args):
@ -509,6 +527,10 @@ If the player is not already close to it, they will go to it.
make more sense from a linguistic perspective (for instance, one could
say "take the flask" rather than just "take flask").
Object can be the name of the object, or its coordinates."""
if self.level == None:
raise GameError("Cannot take: No level has been loaded.")
if self.player == None:
raise GameError("Cannot take: Player character doesn't exist.")
speed = 0.6666667
if args[0] == '-r' or args[0] == 'r' or args[0] == 'run':
speed = 0.3333333
@ -525,16 +547,16 @@ Object can be the name of the object, or its coordinates."""
return
# Similar to go, but not quite the same.
if (x, y) == (self.playerx, self.playery):
self.setEvent(0.125, _ge.TakeEvent(thing))
if (x, y) == (self.player.x, self.player.y):
self.setEvent(0.125, _ge.TakeEvent(self.player, thing))
return
dist, path = self.level.path(x, y, self.playerx, self.playery)
dist, path = self.level.path(x, y, self.player.x, self.player.y)
if dist == -1:
print('{0} cannot reach the {1}.'.format(self.playerName, thing.name), file = self.outstream)
print('{0} cannot reach the {1}.'.format(self.player.name, thing.name), file = self.outstream)
#_hq.heappush(self.eventQueue, (self.gameTime, _ge.NoOpEvent()))
return
else:
pos = self.level.coordsToInt(self.playerx, self.playery)
pos = self.level.coordsToInt(self.player.x, self.player.y)
space = path[pos]
#target = self.level.coordsToInt(x, y)
t = 1
@ -542,23 +564,31 @@ Object can be the name of the object, or its coordinates."""
while space != -1:
newx, newy = self.level.intToCoords(space)
space = path[space]
self.setEvent(t * speed, _ge.GoEvent(self.playerName, newx, newy))
self.setEvent(t * speed, _ge.GoEvent(self.player, newx, newy))
t += 1
#newx, newy = self.level.intToCoords(space)
#_hq.heappush(self.eventQueue, (self.gameTime + t * speed, _ge.GoEvent(self.playerName, newx, newy)))
self.setEvent(t * speed + 0.125, _ge.TakeEvent(thing))
#_hq.heappush(self.eventQueue, (self.gameTime + t * speed, _ge.GoEvent(self.player.name, newx, newy)))
self.setEvent(t * speed + 0.125, _ge.TakeEvent(self.player, thing))
return
def drop(self, args):
"""drop [the] item"""
if self.level == None:
raise GameError("Cannot drop: No level has been loaded.")
if self.player == None:
raise GameError("Cannot drop: Player character doesn't exist.")
if args[0] == 'the':
args.pop(0)
for i in self.playerInv:
thingName = self.playerInv[i].name
if ' '.join(args) == thingName:
self.setEvent(0.0, _ge.DropEvent(self.playerInv[i]))
return
print('{0} does not have a {1}.'.format(self.playerName, args[0]), file = self.outstream)
#for i in self.player.inventory:
# thingName = self.player.inventory[i].name
# if ' '.join(args) == thingName:
# self.setEvent(0.0, _ge.DropEvent(self.player.inventory[i]))
# return
thingName = ' '.join(args)
if thingName in self.player.thingNames:
self.setEvent(0.0, _ge.DropEvent(self.player.getThingByName(thingName)))
else:
print('{0} does not have a {1}.'.format(self.player.name, args[0]), file = self.outstream)
def loadMap(self, args):
# loadMap (fileName)
@ -593,14 +623,26 @@ Object can be the name of the object, or its coordinates."""
print(self.level.openingText, file = self.outstream)
#print(self.outstream.getvalue())
if len(args) <= 2:
self.playerx, self.playery = self.level.playerStart
x, y = self.level.playerStart
if len(args) == 3:
x, y = int(args[1]), int(args[2])
if self.player == None:
# create a player to put in the level.
self.player = _gm.PlayerCharacter(x, y, self.playerDescription, {}, {}, self.playerName)
print("Player created.")
else:
self.playerx, self.playery = int(args[1]), int(args[2])
self.prevx, self.prevy = self.playerx, self.playery
self.player.x, self.player.y = x, y
print("Player moved.")
self.player.prevx, self.player.prevy = self.player.x, self.player.y
self.nextThing = self.level.addThing(self.player, self.nextThing) # The player needs to be added to the new level.
if self.onLevelLoad != None:
self.onLevelLoad()
self.parseScript(self.level.enterScript)
def unloadMap(self, args):
"""Args is only there just in case."""
self.level = None
self.player = None
def saveGame(self, args):
if len(args) < 1:
@ -619,7 +661,7 @@ Object can be the name of the object, or its coordinates."""
self.persist[self.level.name][i] = self.level.getThingByID(i)
# build data object to be saved
data = (self.playerName, self.playerx, self.playery, self.playerInv, self.level.name, self.persist, self.eventQueue, self.customValues, self.gameTime, self.nextThing)
data = (self.player, self.level.name, self.persist, self.eventQueue, self.customValues, self.gameTime, self.nextThing)
# save it!
fileName = 'saves/' + args[0].replace(' ', '_') + '.dat'
@ -650,9 +692,9 @@ Object can be the name of the object, or its coordinates."""
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.customValues, self.gameTime, self.nextThing = _pi.load(f)
self.player, levelname, self.persist, self.eventQueue, self.customValues, self.gameTime, self.nextThing = _pi.load(f)
#print(levelname, x, y, file = self.outstream)
self.loadMap((levelname, x, y))
self.loadMap((levelname, self.player.x, self.player.y))
#_hq.heappush(self.eventQueue, (self.gameTime, _ge.NoOpEvent()))
return
@ -788,41 +830,59 @@ Object can be the name of the object, or its coordinates."""
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:
actor = self.level.getThingByName(e.actor)
self.level.moveThing(actor, e.x, e.y)
#if e.actor == self.playerName:
# self.prevx, self.prevy = self.playerx, self.playery
# self.playerx, self.playery = e.x, e.y
#else:
if e.actor == None:
raise GameError("'Go' event raised for no object.")
self.level.moveThing(e.actor, e.x, e.y)
return False
def handleArrive(self, e):
if e.actor == self.playerName:
self.prevx, self.prevy = self.playerx, self.playery
self.playerx, self.playery = e.x, e.y
print('{0} arrived at {1}{2} after {3:.1f} seconds.'.format(self.playerName, self.numberToLetter(e.x), e.y, e.t), file = self.outstream)
thing = self.level.getThingAtCoords(self.playerx, self.playery)
if thing:
if thing.thingType == 'x':
#if e.actor == self.playerName:
# self.prevx, self.prevy = self.playerx, self.playery
# self.playerx, self.playery = e.x, e.y
# print('{0} arrived at {1}{2} after {3:.1f} seconds.'.format(self.playerName, self.numberToLetter(e.x), e.y, e.t), file = self.outstream)
# thing = self.level.getThingAtCoords(self.playerx, self.playery)
# if thing:
# if thing.thingType == 'x':
# self.parseScript(thing.onUse)
# if (isinstance(thing.key, bool) and thing.key == True) or (isinstance(thing.key, str) and self.parseScript(thing.key)):
# a = self.requestInput('Do you want to go {0}? (Y/n)'.format(str(thing)))
# if a != 'n' and a != 'N':
# self.loadMap((thing.destination, thing.exitid))
# return True
#else:
# actor = self.level.getThingByName(e.actor)
# if actor:
# self.level.moveThing(e.actor, e.x, e.y)
# print('{0} arrived at {1}{2} after {3:.1f} seconds.'.format(actor.name, self.numberToLetter(e.x), e.y, e.t), file = self.outstream)
# thing = self.level.getThingAtCoords(actor.x, actor.y)
# if thing and thing != actor:
# if thing.thingType == 'x':
# print('{0} went {1}.'.format(actor.name, str(thing)))
# self.level.removeThing(actor.name)
# else:
# print('There is nothing by the name of {0}.'.format(e.actor), file = self.outstream)
# return False
if e.actor == None:
raise GameError("'Go' event raised for no object.")
thing = self.level.getThingAtCoords(e.x, e.y)
self.level.moveThing(e.actor, e.x, e.y)
print('{0} arrived at {1}{2} after {3:.1f} seconds.'.format(e.actor.name, self.numberToLetter(e.x), e.y, e.t), file = self.outstream)
if thing:
if thing.thingType == 'x':
if e.actor == self.player:
self.parseScript(thing.onUse)
if (isinstance(thing.key, bool) and thing.key == True) or (isinstance(thing.key, str) and self.parseScript(thing.key)):
a = self.requestInput('Do you want to go {0}? (Y/n)'.format(str(thing)))
if a != 'n' and a != 'N':
self.loadMap((thing.destination, thing.exitid))
return True
else:
actor = self.level.getThingByName(e.actor)
if actor:
self.level.moveThing(e.actor, e.x, e.y)
print('{0} arrived at {1}{2} after {3:.1f} seconds.'.format(actor.name, self.numberToLetter(e.x), e.y, e.t), file = self.outstream)
thing = self.level.getThingAtCoords(actor.x, actor.y)
if thing and thing != actor:
if thing.thingType == 'x':
print('{0} went {1}.'.format(actor.name, str(thing)))
self.level.removeThing(actor.name)
else:
print('There is nothing by the name of {0}.'.format(e.actor), file = self.outstream)
return False
else:
print('{0} went {1}.'.format(actor.name, str(thing)))
self.level.removeThing(actor.name)
return True
def handleUse(self, e):
if e.thing.useFunc == '':
@ -839,15 +899,19 @@ Object can be the name of the object, or its coordinates."""
return False
def handleTake(self, e):
if e.actor == None or e.actor.thingType not in 'np' or e.item == None:
raise GameError("'Take' event cannot be handled.")
self.level.removeThingByID(e.item.thingID)
self.playerInv[e.item.thingID] = e.item
e.actor.addThing(e.item)
return True
def handleDrop(self, e):
e.item.x = self.playerx
e.item.y = self.playery
if e.actor == None or e.actor.thingType not in 'np':
raise GameError("'Drop' event cannot be handled.")
e.item.x = e.actor.x
e.item.y = e.actor.y
self.nextThing = self.level.addThing(e.item, self.nextThing, True) # nextThing shouldn't change
del self.playerInv[e.item.thingID]
self.actor.removeThing(e.item)
return True
def handleBehave(self, e):
@ -890,7 +954,7 @@ Object can be the name of the object, or its coordinates."""
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)
thing.customValues['items'], timeOpen = self.getIO('container')(self.player, items)
return timeOpen
def info(self, thing, args):
@ -901,31 +965,24 @@ Object can be the name of the object, or its coordinates."""
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
inInv = item in self.player.inventory
if not inInv:
print("You cannot wear what you are not carrying.", file = self.outstream)
return 0.0
if 'wearing' not in self.customValues:
self.customValues['wearing'] = {}
if item.customValues['slot'] not in self.customValues['wearing']:
self.customValues['wearing'][item.customValues['slot']] = item
self.customValues['wearing'][item.customValues['slot']] = self.player.removeThing(item)
# This is so a player can't put on a garment, then drop it while
# still also wearing it.
del self.playerInv[item.thingID]
print("{} put on the {}.".format(self.playerName, item.name), file = self.outstream)
print("{} put on the {}.".format(self.player.name, item.name), file = self.outstream)
else: # the player must be wearing something that will get in the way
# put the old clothes back in the inventory
oldItem = self.customValues['wearing'][item.customValues['slot']]
self.playerInv[oldItem.thingID] = oldItem
self.customValues['wearing'][item.customValues['slot']] = item
self.player.addThing(self.customValues['wearing'][item.customValues['slot']])
self.customValues['wearing'][item.customValues['slot']] = self.player.removeThing(item)
# This is so a player can't put on a garment, then drop it while
# still also wearing it.
del self.playerInv[item.thingID]
print("{} took off the {} and put on the {}.".format(self.playerName, oldItem.name, item.name), file = self.outstream)
print("{} took off the {} and put on the {}.".format(self.player.name, oldItem.name, item.name), file = self.outstream)
return 1.0
# item use-on functions
@ -937,8 +994,8 @@ Object can be the name of the object, or its coordinates."""
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
if not thing.passable and self.player.x == thing.x and self.player.y == thing.y:
self.player.x, self.player.y = self.player.prevx, self.player.prevy
else:
print("The key doesn't fit that lock.", file = self.outstream)
return 0.125
@ -1191,7 +1248,7 @@ Object can be the name of the object, or its coordinates."""
equalsign = i.index('=')
cv = i[3:equalsign]
customValues[cv] = self.getValueFromString(i[equalsign+1:])
thing = _gm.NPC(name, x, y, description, behavior, inv, customValues, playerx, playery, False, (bgc, fgc, shape))
thing = _gm.NPC(name, x, y, description, behavior, inv, customValues, playerx, playery, (bgc, fgc, shape))
elif args[0].casefold() == 'door':
# spawn a door
description = 'a nondescript door.'
@ -1303,7 +1360,7 @@ Object can be the name of the object, or its coordinates."""
customValues[cv] = getValueFromString(i[equalsign+1:])
thing = _gm.Item(name, x, y, description, useFunc, useOnFunc, customValues, ranged, (bgc, fgc, shape))
thing.thingID = self.nextThing
self.playerInv[thing.thingID] = thing
self.player.addThing(thing)
self.nextThing += 1
return thing

View file

@ -92,13 +92,15 @@ class UseOnEvent(GameEvent):
self.args = args
class TakeEvent(GameEvent):
def __init__(self, item):
def __init__(self, actor, item):
super(TakeEvent, self).__init__('take')
self.actor = actor
self.item = item
class DropEvent(GameEvent):
def __init__(self, item):
def __init__(self, actor, item):
super(DropEvent, self).__init__('drop')
self.actor = actor
self.item = item
class BehaveEvent(GameEvent):

View file

@ -16,6 +16,8 @@ class Thing(object):
self.y = y
self.playerx = x
self.playery = y
self.prevx = x # if an area gets double-occupied, a thing can get pushed back.
self.prevy = y
if playerx:
self.playerx = playerx
if playery:
@ -179,27 +181,84 @@ class Useable(Thing):
return cls(parts['name'], parts['location'][0], parts['location'][1],
parts['description'], useFunc, customValues, playerx, playery, graphic)
class NPC(Thing):
class Character(Thing):
defaultGraphic = ('clear', '#000000', 'o')
def __init__(self, thingType: str, name: str, x: int, y: int,
description: str, inventory: dict, customValues: dict,
flags: int, playerx = None, playery = None, graphic = defaultGraphic):
super(Character, self).__init__(thingType, name, x, y, description, flags)
if inventory == None:
inventory = {} # create a new dict for the inventory.
# This couldn't be in the NPC constructor because
# then all characters would share a reference to
# the same empty inventory.
self.__inventory = inventory
self.customValues = customValues
self.graphic = graphic
self.thingNames = {}
# set up inventory shtuff
for i in self.__inventory:
if self.__inventory[i].name in self.thingNames:
self.thingNames[self.__inventory[i].name].append(i)
else:
self.thingNames[self.__inventory[i].name] = [i]
def addThing(self, thing):
if not isinstance(thing, Item):
raise TypeError("Only items can be added to a character's inventory.")
self.__inventory[thing.thingID] = thing
if thing.name in self.thingNames:
self.thingNames[thing.name].append(thing.thingID)
else:
self.thingNames[thing.name] = [thing.thingID]
def getThingByID(self, thingID):
return self.__inventory[thingID]
def getThingByName(self, name):
if name in self.thingNames:
return self.__inventory[self.thingNames[name][0]]
else:
return None
def removeThingByID(self, thingID):
ret = self.__inventory[thingID]
self.thingNames[ret.name].remove(thingID)
if len(self.thingNames[ret.name]) == 0:
del self.thingNames[ret.name]
del self.__inventory[thingID]
return ret
def removeThingByName(self, name):
ret = self.getThingByName(name)
self.thingNames[ret.name].remove(thingID)
if len(self.thingNames[ret.name]) == 0:
del self.thingNames[ret.name]
del self.__inventory[thingID]
return ret
def removeThing(self, ret):
self.thingNames[ret.name].remove(ret.thingID)
if len(self.thingNames[ret.name]) == 0:
del self.thingNames[ret.name]
del self.__inventory[ret.thingID]
return ret
@property
def inventory(self):
"""Get the inventory as a list."""
return list(self.__inventory.values())
class NPC(Character):
yaml_flag = u'!NPC'
defaultGraphic = ('clear', '#000000', 'o')
def __init__(self, name, x: int, y: int, description: str, behavior: str, inventory: list, customValues: dict, playerx = None, playery = None, following = False, graphic = defaultGraphic):
super(NPC, self).__init__('c', name, x, y, description, 6, playerx, playery)
def __init__(self, name, x: int, y: int, description: str, behavior: str, inventory: list, customValues: dict, playerx = None, playery = None, graphic = defaultGraphic):
super(NPC, self).__init__('n', name, x, y, description, None, customValues, 6, playerx, playery, graphic)
self.behavior = behavior
self.following = following
self.inventory = inventory
self.customValues = customValues
self.graphic = graphic
self.behaveEvent = None
def give(self, item):
self.inventory.append(item)
def drop(self, index):
return self.inventory.pop(index)
def dialog(self):
pass
self.tempInventory = inventory # should be deleted once NPC is loaded
@classmethod
def to_yaml(cls, representer, node):
@ -217,8 +276,6 @@ class NPC(Thing):
if len(graphic) > 0:
ret['graphic'] = graphic
# save use functions
if node.following:
ret['following'] = node.following
if len(node.inventory) > 0:
ret['inventory'] = node.inventory
if len(node.customValues) > 0:
@ -249,8 +306,6 @@ class NPC(Thing):
shape = parts['graphic']['shape']
graphic = (bgc, fgc, shape)
# load use functions
if 'following' in parts:
useFunc = parts['following']
if 'inventory' in parts:
inventory = parts['inventory']
if 'customValues' in parts:
@ -262,7 +317,7 @@ class NPC(Thing):
playerx, playery = parts['useLocation']
return cls(parts['name'], parts['location'][0], parts['location'][1],
parts['description'], parts['behavior'], inventory, customValues,
playerx, playery, following, graphic)
playerx, playery, graphic)
class Door(Thing):
yaml_flag = u'!Door'
@ -444,6 +499,13 @@ class MapEntrance(Thing):
constructor.construct_mapping(node, parts, True)
# set default values for optional arguments
return cls(parts['location'][0], parts['location'][1], parts['id'])
class PlayerCharacter(Character):
"""Player object. Cannot be created with yaml."""
defaultGraphic = ('clear', '#0000FF', 'o')
def __init__(self, x: int, y: int, description: str, inventory: dict, customValues: dict, name = 'You', graphic = defaultGraphic):
super(PlayerCharacter, self).__init__('p', name, x, y, description, inventory, customValues, 5, graphic=graphic)
class MapError(RuntimeError):
pass
@ -492,86 +554,6 @@ class GameMap(object):
else:
break
return text.replace('\n', end)
@staticmethod
def convert(infile: str):
"""Convert an XML map to a YAML one."""
data = None
with open(infile, 'r') as f:
data = f.read()
info = ET.fromstring(data)
layout = info.find('layout')
ret = {}
if layout == None:
raise MapError('No layout in {0}.'.format(infile))
# "Layout" needs some work before we convert it.
ret['layout'] = GameMap.__cleanStr(layout.text.strip())
# now the rest of the things
if 'openingText' in info.attrib:
ret['openingText'] = info.attrib['openingText']
else:
raise MapError('No opening text in {0}.'.format(infile))
if 'playerStart' in info.attrib:
ps = info.attrib['playerStart'].split(',')
ret['playerStart'] = (int(ps[0]), int(ps[1]))
else:
raise MapError('No player start position in {0}.'.format(infile))
if info.text != None:
ret['description'] = GameMap.__cleanStr(info.text, ' ')
else:
raise MapError('No description in {0}.'.format(infile))
# get map colors
floorColors = ['#9F7F5F']
floorColorsStr = info.find('floorColors')
if floorColorsStr != None:
floorColors = floorColorsStr.text.lstrip().split()
if len(floorColors) == 0:
floorColors.append('#9F7F5F')
ret['floorColors'] = floorColors
wallColors = ['#7F3F0F']
wallColorsStr = info.find('wallColors')
if wallColorsStr != None:
wallColors = wallColorsStr.text.lstrip().split()
if len(wallColors) == 0:
wallColors.append('#7F3F0F')
ret['wallColors'] = wallColors
# get things
ret['loadAlways'] = []
ret['loadOnce'] = []
for node in info:
if node.tag == 'loadOnce':
# Things in the load-once section are only loaded the first
# time that the map is loaded, and saved in the player's
# save file.
for node1 in node:
if node1.tag == 'door':
ret['loadOnce'].append(GameMap.__loadDoor(node1))
elif node1.tag == 'useable':
ret['loadOnce'].append(GameMap.__loadUseable(node1))
elif node1.tag == 'item':
ret['loadOnce'].append(GameMap.__loadItem(node1))
elif node.tag == 'exit':
ret['loadAlways'].append(GameMap.__loadExit(None, node, -1)) # weird arguments: there is no actual level
elif node.tag == 'door':
ret['loadAlways'].append(GameMap.__loadDoor(node))
elif node.tag == 'useable':
ret['loadAlways'].append(GameMap.__loadUseable(node))
#start saving
outfile = infile[:-3] + 'yml'
yaml = ruamel.yaml.YAML()
yaml.indent(mapping=4, sequence=4, offset=2)
yaml.register_class(Item)
yaml.register_class(Useable)
yaml.register_class(Door)
yaml.register_class(MapExit)
with open(outfile, 'w') as f:
f.write('%YAML 1.2\n---\n')
yaml.dump(ret, f)
@staticmethod
def read(infile = None, prevMap = None, preLoaded = False, nextThing = 0):
@ -729,12 +711,15 @@ list of lists of tuples."""
nextThing += 1
# Some things, like containers, have other things as custom values,
# so they need IDs as well.
if thing.thingType in 'iuc':
if thing.thingType in 'iun':
nextThing = self.addThingRecursive(thing.customValues, nextThing)
if thing.thingType == 'c':
for i in thing.inventory:
i.thingID = nextThing
nextThing += 1
if thing.thingType == 'n':
for i in thing.tempInventory:
if i.thingID == -1:
i.thingID = nextThing
nextThing = self.addThingRecursive(i.customValues, nextThing + 1)
thing.addThing(i)
del thing.tempInventory
pos = self.coordsToInt(thing.x, thing.y)
if pos not in self.thingPos:
self.thingPos[pos] = [thing.thingID]
@ -751,8 +736,12 @@ list of lists of tuples."""
def addThingRecursive(self, container, nextThing = 0):
if isinstance(container, Thing):
container.thingID = nextThing
return nextThing + 1
if container.thingID == -1:
container.thingID = nextThing
nextThing = self.addThingRecursive(container.customValues, nextThing)
return nextThing + 1
else:
return nextThing
elif isinstance(container, dict):
for i in container:
nextThing = self.addThingRecursive(container[i], nextThing)
@ -838,6 +827,10 @@ The closeEnough parameter will create a path that lands beside the source if nec
# return -1, [] # meaning you can't get there
def lineOfSight(self, x1, y1, x2, y2):
"""Test for line of signt from one tile to another."""
# Trivial case first:
if abs(x1 - x2) <= 1 and abs(y1 - y2) <= 1:
return True
Dx = x2 - x1
Dy = y2 - y1
y = y1 + 0.5
@ -913,10 +906,7 @@ The closeEnough parameter will create a path that lands beside the source if nec
def getThingsAtPos(self, pos):
if pos in self.thingPos:
ret = []
for i in self.thingPos[pos]:
ret.append(self.things[i])
return ret
return [self.things[i] for i in self.thingPos[pos]]
else:
return []
@ -1019,6 +1009,7 @@ The closeEnough parameter will create a path that lands beside the source if nec
else:
self.thingPos[newPos].append(thing.thingID)
relPlayerx, relPlayery = thing.playerx - thing.x, thing.playery - thing.y
thing.prevx, thing.prevy = thing.x, thing.y
thing.x, thing.y = x, y
thing.playerx, thing.playery = thing.x + relPlayerx, thing.y + relPlayery
else:

View file

@ -46,7 +46,6 @@ class GameShell(Shell):
self.gameBase.registerIO('dialog', self.dialog)
self.gameBase.registerIO('info', self.info)
self.gameBase.registerIO('playercmd', self.playercmd)
self.menuMode()
# Helper functions
@ -148,25 +147,33 @@ If -l is given, a map legend will be printed under the map."""
level = self.gameBase.level
textColor = level.wallColors[0]
floorColor = level.floorColors[0]
priorities = {'p': 1, 'n': 2, 'i': 3, 'u': 4, 'd': 5, 'x': 6, 'a': 7}
for y in range(level.dimensions[1]):
rows.append(['{0}{1:2} {2}{3}'.format(self.clearColor(), y, self.color(textColor[1:]), self.color(floorColor[1:], fg = False))])
rows[-1].append(self.color(textColor[1:]))
for x in range(level.dimensions[0]):
pos = level.mapMatrix[y][x]
thing = level.getThingAtPos(index)
if x == self.gameBase.playerx and y == self.gameBase.playery:
if '#0000FF' != textColor:
textColor = '#0000FF'
rows[-1].append(self.color(textColor[1:]))
rows[-1].append('()')
elif thing:
things = level.getThingsAtPos(index)
#if x == self.gameBase.playerx and y == self.gameBase.playery:
# if self.gameBase.player != textColor:
# textColor = '#0000FF'
# rows[-1].append(self.color(textColor[1:]))
# rows[-1].append('()')
if len(things) > 0:
# Prioritize types: p, n, i, u, d, x, a
thing = things[0]
for i in things[1:]:
if priorities[i.thingType] < priorities[thing.thingType]:
thing = i
if thing.graphic[1] != textColor:
textColor = thing.graphic[1]
rows[-1].append(self.color(textColor[1:]))
if thing.thingType == 'x': # exit
if thing.thingType == 'p': # player
rows[-1].append('()')
elif thing.thingType == 'x': # exit
rows[-1].append('X{0}'.format(thing.exitid))
exits[thing.exitid] = (thing.name, thing.graphic[1])
elif thing.thingType == 'c': # useable
elif thing.thingType == 'n': # NPC
characters[len(characters)+1] = (thing.name, thing.graphic[1])
rows[-1].append('C{0}'.format(len(characters)))
elif thing.thingType == 'd': # door
@ -178,7 +185,7 @@ If -l is given, a map legend will be printed under the map."""
elif thing.thingType == 'i': # item
items[len(items)+1] = (thing.name, thing.graphic[1])
rows[-1].append('I{0}'.format(len(items)))
elif thing.thingType == 'a': # entrance
else: # entrance
rows[-1].append(' ')
elif pos[0] == 'w':
if level.wallColors[level.mapMatrix[y][x][1]] != textColor:
@ -245,12 +252,12 @@ If -l is given, a map legend will be printed under the map."""
ret.append("custom values:")
for i in self.gameBase.customValues:
ret.append("{0:<22}: {1}".format(i, self.gameBase.customValues[i]))
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("Player name:{0:.>68}".format(self.gameBase.player.name))
ret.append("Player position:{0:.>64}".format("{0}{1}".format(self.gameBase.numberToLetter(self.gameBase.player.x), self.gameBase.player.y)))
ret.append("Prev. position:{0:.>65}".format("{0}{1}".format(self.gameBase.numberToLetter(self.gameBase.player.prevx), self.gameBase.player.prevy)))
ret.append("Inventory:")
for i in self.gameBase.playerInv:
ret.append("{0:<8}: {1}".format(i, self.gameBase.playerInv[i].name))
for i in self.gameBase.player.inventory:
ret.append("{0:<8}: {1}".format(i.thingID, i.name))
ret.append("Things:\nID Name X Y")
for i in self.gameBase.level.things:
j = self.gameBase.level.things[i]
@ -263,7 +270,7 @@ If -l is given, a map legend will be printed under the map."""
return
def inv(self, args):
print('\n'.join([self.gameBase.playerInv[i].name for i in self.gameBase.playerInv]))
print('\n'.join([i for i in self.gameBase.player.thingNames]))
def newGame(self, args):
if self.__inGame:
@ -351,22 +358,23 @@ If -l is given, a map legend will be printed under the map."""
# IO calls
def container(self, inv, cont):
"""container IO"""
def container(self, player, cont):
"""container IO
Player is modified through side-effect."""
# Pretty print: get length of the longest inventory item's name
longestLen = 0
for i in inv:
if len(inv[i].name) > longestLen:
longestLen = len(inv[i].name)
for i in player.thingNames:
if len(i) > longestLen:
longestLen = len(i)
if longestLen > 0:
inv = player.inventory # do this assignment because player.inventory is O(n)
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):
print('{{0:<{0}}}{1}'.format(longestLen+2, cont[i].name).format(inv[invKeys[i]].name))
while i < len(inv) and i < len(cont):
print('{{0:<{0}}}{1}'.format(longestLen+2, cont[i].name).format(inv[i].name))
i += 1
while i < len(invKeys):
print(inv[invKeys[i]].name)
while i < len(inv):
print(inv[i].name)
i += 1
while i < len(cont):
print(' '*(longestLen+2) + cont[i].name)
@ -387,26 +395,27 @@ 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].thingID] = cont[i]
player.addThing(cont[i])
del cont[i]
timeSpent += 0.5
print("{0} taken.".format(thing))
break
else:
# If it got here, it didn't find it.
print("No {0} in container.".format(thing))
elif instr[0] == "store":
# store something in the container
if instr[1] == "the":
del instr[1]
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
if thingName in player.thingNames:
cont.append(player.removeThingByName(thingName))
print("{0} stored.".format(thingName))
timeSpent += 0.5
else:
print("No {0} in inventory.".format(thingName))
instr = input("Take, store, or exit: ")
return inv, cont, timeSpent
return cont, timeSpent
def info(self, items):
"""IO for collections of information"""
@ -532,4 +541,5 @@ If -l is given, a map legend will be printed under the map."""
if __name__ == '__main__':
sh = GameShell(GameBase())
sh.menuMode()
sh.run()