Rejiggered players to have a thing in the map, and made the inventory system better.
This commit is contained in:
parent
ed7d265b48
commit
5010042430
4 changed files with 347 additions and 287 deletions
215
gamemap.py
215
gamemap.py
|
@ -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:
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue