Line of sight now works, and is required to look at things. Foundations for NPCs has also been laid.

This commit is contained in:
Patrick Marsee 2019-05-15 17:56:16 -04:00
parent 267f0e9123
commit e0b88e7a45
4 changed files with 151 additions and 17 deletions

View file

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