diff --git a/gamebase.py b/gamebase.py index 06cc4bd..c992716 100644 --- a/gamebase.py +++ b/gamebase.py @@ -235,10 +235,12 @@ 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 thing: - print(self.justifyText(str(thing)), file = self.outstream) - else: + if not self.level.lineOfSight(self.playerx, self.playery, x, y): + print("{} cannot see that.".format(self.playerName)) + 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 use(self, args): diff --git a/gameevents.py b/gameevents.py index 9adeef6..fb61598 100644 --- a/gameevents.py +++ b/gameevents.py @@ -98,3 +98,8 @@ class DropEvent(GameEvent): def __init__(self, item): super(DropEvent, self).__init__('drop') self.item = item + +class BehaveEvent(GameEvent): + def __init__(self, actor): + super(BehaveEvent, self).__init__('behave') + self.actor = actor diff --git a/gamemap.py b/gamemap.py index 27be546..6529fdc 100644 --- a/gamemap.py +++ b/gamemap.py @@ -3,6 +3,7 @@ import re import heapq import xml.etree.ElementTree as ET import ruamel.yaml +import math as _mt from ruamel.yaml.comments import CommentedMap # for loading classes class Thing(object): @@ -121,9 +122,6 @@ class Useable(Thing): self.useFunc = useFunc self.customValues = customValues self.graphic = graphic - - def use(self): - pass @classmethod def to_yaml(cls, representer, node): @@ -182,12 +180,15 @@ class Useable(Thing): parts['description'], useFunc, customValues, playerx, playery, graphic) class NPC(Thing): + yaml_flag = u'!NPC' + defaultGraphic = ('clear', '#000000', 'o') - def __init__(self, name, x: int, y: int, description: str, friendly = True, following = False, graphic = ('clear', '#000000', 'o')): - super(NPC, self).__init__('c', name, x, y, description, 6) + 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) + self.behavior = behavior self.following = following - self.friendly = friendly - self.inventory = [] + self.inventory = inventory + self.customValues = customValues self.graphic = graphic def give(self, item): @@ -198,6 +199,69 @@ class NPC(Thing): def dialog(self): pass + + @classmethod + def to_yaml(cls, representer, node): + # save usual things + ret = {'name': node.name, 'location': (node.x, node.y), + 'description': node.description, 'behavior': node.behavior} + # save graphic + graphic = {} + if node.graphic[0] != NPC.defaultGraphic[0]: + graphic['bgc'] = node.graphic[0] + if node.graphic[1] != NPC.defaultGraphic[1]: + graphic['fgc'] = node.graphic[1] + if node.graphic[2] != NPC.defaultGraphic[2]: + graphic['shape'] = node.graphic[2] + 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: + ret['customValues'] = node.customValues + if node.x != node.playerx or node.y != node.playery: + ret['useLocation'] = (node.playerx, node.playery) + 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 + following = False + inventory = [] + customValues = {} + playerx, playery = parts['location'] + bgc = NPC.defaultGraphic[0] + fgc = NPC.defaultGraphic[1] + shape = NPC.defaultGraphic[2] + # load graphic + if 'graphic' in parts: + if 'bgc' in parts['graphic']: + bgc = parts['graphic']['bgc'] + if 'fgc' in parts['graphic']: + fgc = parts['graphic']['fgc'] + if 'shape' in parts['graphic']: + 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: + customValues = dict(parts['customValues']) + for v in customValues: + if isinstance(customValues[v], tuple): + customValues[v] = list(customValues[v]) + if 'useLocation' in parts: + playerx, playery = parts['useLocation'] + return cls(parts['name'], parts['location'][0], parts['location'][1], + parts['description'], parts['behavior'], inventory, customValues, + playerx, playery, following, graphic) class Door(Thing): yaml_flag = u'!Door' @@ -713,19 +777,65 @@ 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): - pass + Dx = x2 - x1 + Dy = y2 - y1 + y = y1 + 0.5 + x = x1 + 0.5 + lst = [] + if abs(Dx) >= abs(Dy): + if Dx < 0: + x = x2 + 0.5 + y = y2 + 0.5 + dy = Dy / Dx + while(x < max(x2, x1) - 0.5): + x += 1 + lst.append(self.coordsToInt(_mt.floor(x), _mt.floor(y))) + if _mt.floor(y) != _mt.floor(y + dy): + lst.append(self.coordsToInt(_mt.floor(x), _mt.floor(y + dy))) + y += dy + elif abs(Dx) < abs(Dy): + if Dy < 0: + x = x2 + 0.5 + y = y2 + 0.5 + dx = Dx / Dy + while(y < max(y2, y1) - 0.5): + y += 1 + lst.append(self.coordsToInt(_mt.floor(x), _mt.floor(y))) + if _mt.floor(x) != _mt.floor(x + dx): + lst.append(self.coordsToInt(_mt.floor(x + dx), _mt.floor(y))) + x += dx + + # Here is where we actually check: + for space in lst: + if not self.isPassable(space): + return False + return True + + def isPassable(self, x, y = -1): + pos = x + if y == -1: + x, y = self.intToCoords(x) + else: + pos = self.coordsToInt(x, y) + if self.mapMatrix[y][x][0] == 'w': + return False + thingsInSpace = self.getThingsAtPos(pos) + for thing in thingsInSpace: + if not thing.passable: + return False + return True @staticmethod def __coordsToInt(x, y, width): return x + y * width - def coordsToInt(self, x, y, width = -1): + def coordsToInt(self, x: int, y: int, width = -1): if width < 0: return x + y * self.dimensions[0] else: return x + y * width - def intToCoords(self, pos): + def intToCoords(self, pos: int): return pos % self.dimensions[0], int(pos / self.dimensions[0]) def getThingAtCoords(self, x, y): @@ -747,7 +857,7 @@ The closeEnough parameter will create a path that lands beside the source if nec ret.append(self.things[i]) return ret else: - return None + return [] def getThingByName(self, name): if name in self.thingNames: @@ -762,7 +872,7 @@ The closeEnough parameter will create a path that lands beside the source if nec ret.append(self.things[i]) return ret else: - return None + return [] def getThingByID(self, thingID): if thingID in self.things: @@ -806,7 +916,7 @@ The closeEnough parameter will create a path that lands beside the source if nec ret.append(self.removeThingByThing(self.things[i])) return ret else: - return None + return [] def removeThingByName(self, name): if name in self.thingNames: @@ -821,7 +931,7 @@ The closeEnough parameter will create a path that lands beside the source if nec ret.append(self.removeThingByThing(self.things[i])) return ret else: - return None + return [] def removeThingByID(self, thingID): if thingID in self.things: @@ -845,7 +955,9 @@ The closeEnough parameter will create a path that lands beside the source if nec self.thingPos[newPos] = [name] else: self.thingPos[newPos].append(name) + relPlayerx, relPlayery = thing.playerx - thing.x, thing.playery - thing.y thing.x, thing.y = x, y + thing.playerx, thing.playery = thing.x + relPlayerx, thing.y + relPlayery else: raise RuntimeError("There is nothing to move.".format(name)) diff --git a/testing/testDialog.yml b/testing/testDialog.yml new file mode 100644 index 0000000..80dabcf --- /dev/null +++ b/testing/testDialog.yml @@ -0,0 +1,15 @@ +%YAML 1.2 +--- +# This is a sample dialog tree, represented as nested dicts in YAML. +opener: Hello, I'm a sample-dialog NPC. I assure you, I eventually turn + out to be a really well-rounded character. +action: answer +answers: + - Okay. + - Really? Well, tell me more! +replies: + - opener: Yup. + action: exit + - opener: I would, but there's still a bunch of foreshadowing and stuff we + have to get through first. + action: back