529 lines
20 KiB
Python
529 lines
20 KiB
Python
#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)
|