Added loci, and made dialog processing internal to the engine.
This commit is contained in:
parent
5010042430
commit
9cda61a895
11 changed files with 1860 additions and 1269 deletions
685
gamemap.py
685
gamemap.py
|
@ -1,511 +1,10 @@
|
|||
#gamemap.py
|
||||
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):
|
||||
|
||||
def __init__(self, thingType: str, name: str, x: int, y: int, description: str, flags: int, playerx = None, playery = None):
|
||||
self.thingType = thingType
|
||||
self.name = name
|
||||
self.description = description
|
||||
self.x = x
|
||||
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:
|
||||
self.playery = playery
|
||||
self.passable = bool(flags & 1)
|
||||
self.talkable = bool(flags & 2)
|
||||
self.lookable = bool(flags & 4)
|
||||
self.takeable = bool(flags & 8)
|
||||
self.useable = bool(flags & 16)
|
||||
self.graphic = ('clear', '#7F7F7F', ' ')
|
||||
self.thingID = -1 # ID not assigned
|
||||
|
||||
def __str__(self):
|
||||
"""__str__ is used for look."""
|
||||
return self.description
|
||||
|
||||
def __eq__(self, other):
|
||||
if not isinstance(other, Thing):
|
||||
return False
|
||||
return self.name == other.name
|
||||
|
||||
class Item(Thing):
|
||||
yaml_flag = u'!Item'
|
||||
defaultGraphic = ('clear', '#00BF00', '^')
|
||||
|
||||
def __init__(self, name, x: int, y: int, description: str, useFunc: str, useOnFunc: str, customValues: dict, ranged: bool, graphic = defaultGraphic):
|
||||
super(Item, self).__init__('i', name, x, y, description, 13)
|
||||
self.useFunc = useFunc
|
||||
self.useOnFunc = useOnFunc
|
||||
self.customValues = customValues
|
||||
self.ranged = ranged
|
||||
self.graphic = graphic
|
||||
|
||||
def use(self):
|
||||
pass
|
||||
|
||||
@classmethod
|
||||
def to_yaml(cls, representer, node):
|
||||
# save usual things
|
||||
ret = {'name': node.name, 'location': (node.x, node.y), 'description': node.description}
|
||||
# save graphic
|
||||
graphic = {}
|
||||
if node.graphic[0] != Item.defaultGraphic[0]:
|
||||
graphic['bgc'] = node.graphic[0]
|
||||
if node.graphic[1] != Item.defaultGraphic[1]:
|
||||
graphic['fgc'] = node.graphic[1]
|
||||
if node.graphic[2] != Item.defaultGraphic[2]:
|
||||
graphic['shape'] = node.graphic[2]
|
||||
if len(graphic) > 0:
|
||||
ret['graphic'] = graphic
|
||||
# save use functions
|
||||
if node.useFunc != '':
|
||||
ret['useFunc'] = node.useFunc
|
||||
if node.useOnFunc != '':
|
||||
ret['useOnFunc'] = node.useOnFunc
|
||||
if len(node.customValues) > 0:
|
||||
ret['customValues'] = node.customValues
|
||||
if node.ranged:
|
||||
ret['ranged'] = node.ranged
|
||||
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
|
||||
useFunc = ''
|
||||
useOnFunc = ''
|
||||
customValues = {}
|
||||
ranged = False
|
||||
bgc = Item.defaultGraphic[0]
|
||||
fgc = Item.defaultGraphic[1]
|
||||
shape = Item.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 'useFunc' in parts:
|
||||
useFunc = parts['useFunc']
|
||||
if 'useOnFunc' in parts:
|
||||
useOnFunc = parts['useOnFunc']
|
||||
if 'customValues' in parts:
|
||||
customValues = dict(parts['customValues'])
|
||||
for v in customValues:
|
||||
if isinstance(customValues[v], tuple):
|
||||
customValues[v] = list(customValues[v])
|
||||
if 'ranged' in parts:
|
||||
useOnFunc = parts['ranged']
|
||||
return cls(parts['name'], parts['location'][0], parts['location'][1],
|
||||
parts['description'], useFunc, useOnFunc, customValues, ranged, graphic)
|
||||
|
||||
class Useable(Thing):
|
||||
yaml_flag = u'!Useable'
|
||||
defaultGraphic = ('clear', '#0000FF', '#')
|
||||
|
||||
def __init__(self, name, x: int, y: int, description: str, useFunc: str, customValues: dict, playerx = None, playery = None, graphic = defaultGraphic):
|
||||
super(Useable, self).__init__('u', name, x, y, description, 16, playerx, playery)
|
||||
self.useFunc = useFunc
|
||||
self.customValues = customValues
|
||||
self.graphic = graphic
|
||||
|
||||
@classmethod
|
||||
def to_yaml(cls, representer, node):
|
||||
# save usual things
|
||||
ret = {'name': node.name, 'location': (node.x, node.y), 'description': node.description}
|
||||
# save graphic
|
||||
graphic = {}
|
||||
if node.graphic[0] != Useable.defaultGraphic[0]:
|
||||
graphic['bgc'] = node.graphic[0]
|
||||
if node.graphic[1] != Useable.defaultGraphic[1]:
|
||||
graphic['fgc'] = node.graphic[1]
|
||||
if node.graphic[2] != Useable.defaultGraphic[2]:
|
||||
graphic['shape'] = node.graphic[2]
|
||||
if len(graphic) > 0:
|
||||
ret['graphic'] = graphic
|
||||
# save use functions
|
||||
if node.useFunc != '':
|
||||
ret['useFunc'] = node.useFunc
|
||||
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
|
||||
useFunc = ''
|
||||
customValues = {}
|
||||
playerx, playery = parts['location']
|
||||
bgc = Useable.defaultGraphic[0]
|
||||
fgc = Useable.defaultGraphic[1]
|
||||
shape = Useable.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 'useFunc' in parts:
|
||||
useFunc = parts['useFunc']
|
||||
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'], useFunc, customValues, playerx, playery, graphic)
|
||||
|
||||
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, graphic = defaultGraphic):
|
||||
super(NPC, self).__init__('n', name, x, y, description, None, customValues, 6, playerx, playery, graphic)
|
||||
self.behavior = behavior
|
||||
self.behaveEvent = None
|
||||
self.tempInventory = inventory # should be deleted once NPC is loaded
|
||||
|
||||
@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 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 '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, graphic)
|
||||
|
||||
class Door(Thing):
|
||||
yaml_flag = u'!Door'
|
||||
defaultGraphic = ('clear', '#7F3F00', '#')
|
||||
|
||||
def __init__(self, name, x: int, y: int, locked: bool, description = None, key = None, graphic = defaultGraphic):
|
||||
self.descBase = description
|
||||
if description == None:
|
||||
if locked:
|
||||
description = "The {0} is locked.".format(name)
|
||||
else:
|
||||
description = "The {0} is unlocked.".format(name)
|
||||
else:
|
||||
if locked:
|
||||
description += " It is locked.".format(name)
|
||||
else:
|
||||
description += " It is unlocked.".format(name)
|
||||
super(Door, self).__init__('d', name, x, y, description, 1)
|
||||
self.passable = not locked
|
||||
self.key = key
|
||||
self.graphic = graphic
|
||||
|
||||
def lock(self, key = None):
|
||||
if key == self.key:
|
||||
self.passable = not self.passable
|
||||
if self.descBase == None:
|
||||
if self.passable:
|
||||
self.description = "The {0} is unlocked.".format(self.name)
|
||||
else:
|
||||
self.description = "The {0} is locked.".format(self.name)
|
||||
else:
|
||||
if self.passable:
|
||||
self.description += " It is unlocked.".format(self.name)
|
||||
else:
|
||||
self.description += " It is locked.".format(self.name)
|
||||
return True
|
||||
return False
|
||||
|
||||
@classmethod
|
||||
def to_yaml(cls, representer, node):
|
||||
# save usual things
|
||||
ret = {'name': node.name, 'location': (node.x, node.y)}
|
||||
# save graphic
|
||||
graphic = {}
|
||||
if node.graphic[0] != Door.defaultGraphic[0]:
|
||||
graphic['bgc'] = node.graphic[0]
|
||||
if node.graphic[1] != Door.defaultGraphic[1]:
|
||||
graphic['fgc'] = node.graphic[1]
|
||||
if node.graphic[2] != Door.defaultGraphic[2]:
|
||||
graphic['shape'] = node.graphic[2]
|
||||
if len(graphic) > 0:
|
||||
ret['graphic'] = graphic
|
||||
# save door state
|
||||
if node.passable:
|
||||
ret['locked'] = not node.passable
|
||||
if node.descBase != None:
|
||||
ret['description'] = node.descBase
|
||||
if node.key != None:
|
||||
ret['key'] = node.key
|
||||
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
|
||||
description = None
|
||||
locked = False
|
||||
key = None
|
||||
bgc = Door.defaultGraphic[0]
|
||||
fgc = Door.defaultGraphic[1]
|
||||
shape = Door.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 door state
|
||||
if 'description' in parts:
|
||||
description = parts['description']
|
||||
if 'locked' in parts:
|
||||
locked = parts['locked']
|
||||
if 'key' in parts:
|
||||
key = parts['key']
|
||||
return cls(parts['name'], parts['location'][0], parts['location'][1],
|
||||
locked, description, key, graphic)
|
||||
|
||||
class MapExit(Thing):
|
||||
yaml_flag = u'!MapExit'
|
||||
defaultGraphic = ('clear', '#FF0000', 'x')
|
||||
|
||||
def __init__(self, name, x: int, y: int, exitid: int, destination: str, prefix = None, onUse = '', key = True, graphic = defaultGraphic):
|
||||
description = name
|
||||
if prefix:
|
||||
description = "{0} {1}".format(prefix, name)
|
||||
super(MapExit, self).__init__('x', name, x, y, description, 5)
|
||||
self.exitid = exitid
|
||||
self.destination = destination
|
||||
self.prefix = prefix
|
||||
self.onUse = onUse
|
||||
self.key = key
|
||||
self.graphic = graphic
|
||||
|
||||
@classmethod
|
||||
def to_yaml(cls, representer, node):
|
||||
# save usual things
|
||||
ret = {'name': node.name, 'location': (node.x, node.y), 'id': node.exitid, 'destination': node.destination}
|
||||
# save graphic
|
||||
graphic = {}
|
||||
if node.graphic[0] != MapExit.defaultGraphic[0]:
|
||||
graphic['bgc'] = node.graphic[0]
|
||||
if node.graphic[1] != MapExit.defaultGraphic[1]:
|
||||
graphic['fgc'] = node.graphic[1]
|
||||
if node.graphic[2] != MapExit.defaultGraphic[2]:
|
||||
graphic['shape'] = node.graphic[2]
|
||||
if len(graphic) > 0:
|
||||
ret['graphic'] = graphic
|
||||
if node.prefix != None:
|
||||
ret['prefix'] = node.prefix
|
||||
if node.onUse != '':
|
||||
ret['onUse'] = node.onUse
|
||||
if node.key != True:
|
||||
ret['key'] = node.key
|
||||
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
|
||||
prefix = None
|
||||
onUse = ''
|
||||
key = True
|
||||
bgc = MapExit.defaultGraphic[0]
|
||||
fgc = MapExit.defaultGraphic[1]
|
||||
shape = MapExit.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)
|
||||
if 'prefix' in parts:
|
||||
prefix = parts['prefix']
|
||||
if 'onUse' in parts:
|
||||
onUse = parts['onUse']
|
||||
if 'key' in parts:
|
||||
key = parts['key']
|
||||
return cls(parts['name'], parts['location'][0], parts['location'][1],
|
||||
parts['id'], parts['destination'], prefix, onUse, key, graphic)
|
||||
|
||||
class MapEntrance(Thing):
|
||||
yaml_flag = u'!MapEntrance'
|
||||
defaultGraphic = ('clear', 'clear', 'x')
|
||||
# no graphic - should not be drawn
|
||||
|
||||
def __init__(self, x: int, y: int, exitid: int, name = None):
|
||||
if name == None:
|
||||
name = 'entrance {}'.format(exitid)
|
||||
super(MapEntrance, self).__init__('a', name, x, y, '', 1)
|
||||
self.exitid = exitid
|
||||
|
||||
@classmethod
|
||||
def to_yaml(cls, representer, node):
|
||||
# save usual things
|
||||
ret = {'location': (node.x, node.y), 'id': node.exitid}
|
||||
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
|
||||
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)
|
||||
import gamethings as _gt
|
||||
import gamelocus as _gl
|
||||
|
||||
class MapError(RuntimeError):
|
||||
pass
|
||||
|
@ -518,6 +17,13 @@ class GameMap(object):
|
|||
# regular expressions
|
||||
tileRegex = re.compile(r'([a-z ])([0-9]+|[ ])')
|
||||
matrixRegex = re.compile(r'(?:[ \t]*(?:[a-z ](?:[0-9]+|[ ]))+(\n))+')
|
||||
yaml = ruamel.yaml.YAML()
|
||||
yaml.register_class(_gt.Item)
|
||||
yaml.register_class(_gt.Useable)
|
||||
yaml.register_class(_gt.NPC)
|
||||
yaml.register_class(_gt.Door)
|
||||
yaml.register_class(_gt.MapExit)
|
||||
yaml.register_class(_gt.MapEntrance)
|
||||
|
||||
def __init__(self, name, graph, matrix, dimensions):
|
||||
self.name = name
|
||||
|
@ -534,6 +40,7 @@ class GameMap(object):
|
|||
self.wallColors = []
|
||||
self.persistent = []
|
||||
self.enterScript = ''
|
||||
self.version = 'square 1'
|
||||
|
||||
@staticmethod
|
||||
def __cleanStr(text: str, end = '\n'):
|
||||
|
@ -562,22 +69,23 @@ it will read from stdin. Otherwise, it should be a valid file name.
|
|||
Entering a map through stdin will be obsolete once testing is over."""
|
||||
info = None
|
||||
tryToRead = True
|
||||
yaml = ruamel.yaml.YAML()
|
||||
yaml.register_class(Item)
|
||||
yaml.register_class(Useable)
|
||||
yaml.register_class(NPC)
|
||||
yaml.register_class(Door)
|
||||
yaml.register_class(MapExit)
|
||||
yaml.register_class(MapEntrance)
|
||||
if infile != None:
|
||||
try:
|
||||
with open(infile, 'r') as f:
|
||||
info = yaml.load(f)
|
||||
info = GameMap.yaml.load(f)
|
||||
except OSError as e:
|
||||
print("The file could not be read.")
|
||||
return None, nextThing
|
||||
else:
|
||||
raise MapError("No file was specified for loading.")
|
||||
|
||||
# Future feature: different map standards, with cool special features
|
||||
# like hex tiles, or walls in-between tiles.
|
||||
# For now just 'square 1': square tiles, version 1.
|
||||
version = 'square 1'
|
||||
if 'version' in info:
|
||||
if info['version'] in ('square 1'):
|
||||
version = info['version']
|
||||
|
||||
# Now what we do with the data
|
||||
mat = None
|
||||
|
@ -735,7 +243,7 @@ list of lists of tuples."""
|
|||
return nextThing
|
||||
|
||||
def addThingRecursive(self, container, nextThing = 0):
|
||||
if isinstance(container, Thing):
|
||||
if isinstance(container, _gt.Thing):
|
||||
if container.thingID == -1:
|
||||
container.thingID = nextThing
|
||||
nextThing = self.addThingRecursive(container.customValues, nextThing)
|
||||
|
@ -777,42 +285,55 @@ list of lists of tuples."""
|
|||
else:
|
||||
raise ValueError('Thing cannot be found by {}.'.format(str(kwargs)))
|
||||
|
||||
def path(self, x1, y1, x2, y2, closeEnough = True):
|
||||
def path(self, x1, y1, loc, closeEnough = True):
|
||||
startThing = self.getThingAtCoords(x1, y1)
|
||||
if not closeEnough:
|
||||
if startThing and not startThing.passable:
|
||||
return -1, [] # meaning you can't get there
|
||||
dist, prev = self.dijkstra(x1, y1, closeEnough)
|
||||
endPoint = self.coordsToInt(x2, y2)
|
||||
#if not closeEnough:
|
||||
# if startThing and not startThing.passable:
|
||||
# return -1, [], -1 # meaning you can't get there
|
||||
dist, prev, endPoint = self.dijkstra(x1, y1, loc, closeEnough)
|
||||
#endPoint = self.coordsToInt(x2, y2)
|
||||
numVertex = self.dimensions[0] * self.dimensions[1]
|
||||
if dist[endPoint] < numVertex + 1:
|
||||
return dist[endPoint], prev
|
||||
if endPoint > -1 and dist[endPoint] < numVertex + 1:
|
||||
pathList = [endPoint]
|
||||
nextPoint = prev[endPoint]
|
||||
while nextPoint != -1:
|
||||
pathList.append(nextPoint)
|
||||
nextPoint = prev[nextPoint]
|
||||
pathList.reverse()
|
||||
return dist[endPoint], pathList[1:], endPoint
|
||||
else:
|
||||
return -1, [] # meaning you can't get there
|
||||
return -1, [], -1 # meaning you can't get there
|
||||
|
||||
def dijkstra(self, x1, y1, closeEnough = True):
|
||||
def dijkstra(self, x1, y1, loc = None, closeEnough = True):
|
||||
"""Uses Dijkstra's Algorithm to find the shortest path from (x1, y1) to (x2, y2)
|
||||
The closeEnough parameter will create a path that lands beside the source if necessary."""
|
||||
The closeEnough parameter will create a path that lands beside the source if
|
||||
necessary. The loc parameter is an optional locus which will cause the function
|
||||
to return once it finds a point that's in the locus."""
|
||||
# first test to see that the start point is passable
|
||||
startThing = self.getThingAtCoords(x1, y1)
|
||||
startPoint = self.coordsToInt(x1, y1)
|
||||
#endPoint = self.coordsToInt(x2, y2)
|
||||
endPoint = -1 # until one matches the locus, which it might not.
|
||||
numVertex = self.dimensions[0] * self.dimensions[1]
|
||||
dist = [numVertex + 1 for i in range(numVertex)]
|
||||
prev = [-1 for i in range(numVertex)]
|
||||
dist[startPoint] = 0
|
||||
if closeEnough:
|
||||
if startThing and not startThing.passable:
|
||||
dist[startPoint] = -1
|
||||
#if closeEnough:
|
||||
# if startThing and not startThing.passable:
|
||||
# dist[startPoint] = -1 # This is so it doesn't path into a non-passable end point.
|
||||
queue = []
|
||||
heapq.heappush(queue, (dist[startPoint], startPoint))
|
||||
|
||||
while len(queue) > 0:
|
||||
u = heapq.heappop(queue)[1]
|
||||
if loc != None and self.intToCoords(u) in loc:
|
||||
return dist, prev, u
|
||||
for v in self.mapGraph[u]:
|
||||
thing = self.getThingAtPos(v)
|
||||
if thing and not thing.passable:
|
||||
continue
|
||||
if closeEnough and self.intToCoords(v) in loc:
|
||||
return dist, prev, u
|
||||
else:
|
||||
continue
|
||||
tempDist = dist[u] + 1
|
||||
if tempDist < dist[v]:
|
||||
dist[v] = tempDist
|
||||
|
@ -820,7 +341,7 @@ The closeEnough parameter will create a path that lands beside the source if nec
|
|||
prev[v] = u
|
||||
heapq.heappush(queue, (dist[v], v))
|
||||
|
||||
return dist, prev
|
||||
return dist, prev, endPoint
|
||||
#if dist[endPoint] < numVertex + 1:
|
||||
# return dist[endPoint], prev
|
||||
#else:
|
||||
|
@ -831,37 +352,13 @@ The closeEnough parameter will create a path that lands beside the source if nec
|
|||
# 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
|
||||
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
|
||||
|
||||
# Common case second:
|
||||
lst = list(_gl.LineLocus(x1, y1, x2, y2, False))[1:-1]
|
||||
|
||||
# Here is where we actually check:
|
||||
for space in lst:
|
||||
if not self.isPassable(space):
|
||||
if not self.isPassable(self.coordsToInt(*space)):
|
||||
return False
|
||||
return True
|
||||
|
||||
|
@ -999,6 +496,8 @@ The closeEnough parameter will create a path that lands beside the source if nec
|
|||
else:
|
||||
x, y = self.intToCoords(x)
|
||||
if thing != None:
|
||||
if thing.x == x and thing.y == y:
|
||||
return # it's already there, so don't do anything.
|
||||
oldPos = self.coordsToInt(thing.x, thing.y)
|
||||
if oldPos in self.thingPos:
|
||||
self.thingPos[oldPos].remove(thing.thingID)
|
||||
|
@ -1015,3 +514,75 @@ The closeEnough parameter will create a path that lands beside the source if nec
|
|||
else:
|
||||
raise MapError("There is nothing to move.")
|
||||
|
||||
class LoSLocus(_gl.Locus):
|
||||
"""A locus that defines all points within line-of-sight of a given points."""
|
||||
|
||||
def __init__(self, x, y, level):
|
||||
self.x = x
|
||||
self.y = y
|
||||
self.level = level
|
||||
|
||||
def __contains__(self, item):
|
||||
if not isinstance(item, tuple) and not isinstance(item, list):
|
||||
raise ValueError("Item must be a tuple or a list.")
|
||||
x, y = item
|
||||
return self.level.lineOfSight(x, y, self.x, self.y)
|
||||
|
||||
def __iter__(self):
|
||||
ret = [(self.x, self.y)]
|
||||
mat = [[False for i in range(self.level.y)] for j in range(self.level.x)]
|
||||
mat[self.x][self.y] = True
|
||||
fringe = [(self.x, self.y + 1), (self.x + 1, self.y), (self.x, self.y - 1), (self.x - 1, self.y),
|
||||
(self.x + 1, self.y + 1), (self.x + 1, self.y - 1), (self.x - 1, self.y - 1), (self.x - 1, self.y + 1)]
|
||||
while len(fringe) > 0:
|
||||
point = fringe.pop(0)
|
||||
check = []
|
||||
if abs(point[0] - self.x) > abs(point[1] - self.y):
|
||||
if point[0] > self.x:
|
||||
check.append((point[0] - 1, point[1]))
|
||||
if point[1] > self.y:
|
||||
check.append((point[0] - 1, point[1] - 1))
|
||||
elif point[1] < self.y:
|
||||
check.append((point[0] - 1, point[1] + 1))
|
||||
else:
|
||||
check.append((point[0] + 1, point[1]))
|
||||
if point[1] > self.y:
|
||||
check.append((point[0] + 1, point[1] - 1))
|
||||
elif point[1] < self.y:
|
||||
check.append((point[0] + 1, point[1] + 1))
|
||||
elif abs(point[0] - self.x) < abs(point[1] - self.y):
|
||||
if point[1] > self.y:
|
||||
check.append((point[0], point[1] - 1))
|
||||
if point[0] > self.x:
|
||||
check.append((point[0] - 1, point[1] - 1))
|
||||
elif point[0] < self.x:
|
||||
check.append((point[0] + 1, point[1] - 1))
|
||||
else:
|
||||
check.append((point[0], point[1] + 1))
|
||||
if point[0] > self.x:
|
||||
check.append((point[0] - 1, point[1] + 1))
|
||||
elif point[0] < self.x:
|
||||
check.append((point[0] + 1, point[1] + 1))
|
||||
else:
|
||||
if point[0] > self.x:
|
||||
if point[1] > self.y:
|
||||
check.append((point[0] - 1, point[1] - 1))
|
||||
else:
|
||||
check.append((point[0] - 1, point[1] - 1))
|
||||
else:
|
||||
if point[1] > self.y:
|
||||
check.append((point[0] + 1, point[1] - 1))
|
||||
else:
|
||||
check.append((point[0] + 1, point[1] - 1))
|
||||
status = [mat[i[0]][i[1]] for i in check]
|
||||
addIf = False
|
||||
if True in status:
|
||||
if False in status:
|
||||
addIf = self.level.lineOfSight(point[0], point[1], self.x, self.y)
|
||||
else:
|
||||
addIf = True
|
||||
if addIf:
|
||||
mat[point[0]][point[1]] = self.level.isPassable(*point)
|
||||
ret.append(point)
|
||||
return iter(ret)
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue