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
529
gamethings.py
Normal file
529
gamethings.py
Normal file
|
@ -0,0 +1,529 @@
|
|||
#gamethings.py
|
||||
|
||||
import ruamel.yaml
|
||||
from ruamel.yaml.comments import CommentedMap
|
||||
|
||||
class Thing(object):
|
||||
|
||||
def __init__(self, thingType: str, name: str, x: int, y: int, description: str, flags: int, playerx = None, playery = None, **kwargs):
|
||||
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 Observer(Thing):
|
||||
"""ABC for things that have a dict of events that they should listen to."""
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
super().__init__(**kwargs)
|
||||
self.behaviors = {} # {the name of the event : (behaviorQueue priority, the name of the behavior)}
|
||||
self.busy = False # Prevents a new behavior until the current one is finished
|
||||
self.behaviorQueue = [] # If you can't make it perfect, then make it adjustable.
|
||||
# The behavior queue is for behaviors that need to execute once the current action is finished.
|
||||
# If it's not considered important enough (behaviors[event][0] < 0), it's dropped.
|
||||
|
||||
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)
|
||||
|
||||
def use(self, user, gameBase, args) -> float:
|
||||
pass
|
||||
|
||||
def useOn(self, user, gameBase, thing, args) -> float:
|
||||
pass
|
||||
|
||||
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)
|
||||
|
||||
def use(self, user, gameBase, args) -> float:
|
||||
pass
|
||||
|
||||
class Character(Thing):
|
||||
defaultGraphic = ('clear', '#000000', 'o')
|
||||
|
||||
def __init__(self, inventory: dict, customValues: dict, graphic = defaultGraphic, **kwargs):
|
||||
super(Character, self).__init__(**kwargs)
|
||||
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, Observer):
|
||||
yaml_flag = u'!NPC'
|
||||
defaultGraphic = ('clear', '#000000', 'o')
|
||||
|
||||
def __init__(self, behaviors: dict, tempInventory: list, **kwargs):
|
||||
if 'graphic' not in kwargs:
|
||||
kwargs['graphic'] = PlayerCharacter.defaultGraphic
|
||||
super(NPC, self).__init__(thingType = 'n', inventory = {}, flags = 6, **kwargs)
|
||||
self.behaviors = behaviors
|
||||
self.behaveEvent = None
|
||||
self.tempInventory = tempInventory # 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, 'behaviors': node.behaviors}
|
||||
# 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
|
||||
minventory = []
|
||||
mcustomValues = {}
|
||||
mplayerx, mplayery = 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']
|
||||
mgraphic = (bgc, fgc, shape)
|
||||
# load use functions
|
||||
if 'inventory' in parts:
|
||||
inventory = parts['inventory']
|
||||
if 'customValues' in parts:
|
||||
mcustomValues = dict(parts['customValues'])
|
||||
for v in mcustomValues:
|
||||
if isinstance(mcustomValues[v], tuple):
|
||||
mcustomValues[v] = list(mcustomValues[v])
|
||||
if 'useLocation' in parts:
|
||||
playerx, playery = parts['useLocation']
|
||||
return cls(name = parts['name'], x = parts['location'][0], y = parts['location'][1],
|
||||
description = parts['description'], behaviors = parts['behaviors'], tempInventory = minventory, customValues = mcustomValues,
|
||||
playerx = mplayerx, plyery = mplayery, graphic = mgraphic)
|
||||
|
||||
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, **kwargs):
|
||||
if 'name' not in kwargs:
|
||||
kwargs['name'] == 'You'
|
||||
if 'graphic' not in kwargs:
|
||||
kwargs['graphic'] = PlayerCharacter.defaultGraphic
|
||||
super(PlayerCharacter, self).__init__(thingType = 'p', flags = 5, **kwargs)
|
Loading…
Add table
Add a link
Reference in a new issue