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

@ -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: