diff --git a/gamebase.py b/gamebase.py index b30ef64..98dfcda 100644 --- a/gamebase.py +++ b/gamebase.py @@ -44,7 +44,7 @@ class GameBase(object): self.eventQueue = [] self.gameTime = 0.0 self.skipLoop = True - self.nextThing = 0 + self.nextThing = 1 # player info self.playerName = 'You' @@ -363,9 +363,9 @@ Object can be the name of the object, or its coordinates.""" # load the new level if len(args) == 2: - self.level, self.nextThing = _gm.GameMap.read(args[0], int(args[1]), preLoaded, self.nextThing) + self.level, self.nextThing = _gm.GameMap.read(args[0], int(args[1]), self.singletons, preLoaded, self.nextThing) else: - self.level, self.nextThing = _gm.GameMap.read(args[0], None, preLoaded, self.nextThing) + self.level, self.nextThing = _gm.GameMap.read(args[0], None, self.singletons, preLoaded, self.nextThing) if self.level == None: raise GameError("Map could not be loaded.") @@ -388,7 +388,7 @@ Object can be the name of the object, or its coordinates.""" inventory = {}, customValues = {}, name = self.playerName) #print("Player created.") else: - self.player.x, self.player.y = x, y + self.player.x, self.player.y = mx, my #print("Player moved.") self.player.prevx, self.player.prevy = self.player.x, self.player.y self.nextThing = self.level.addThing(self.player, self.nextThing) # The player needs to be added to the new level. @@ -559,6 +559,7 @@ Object can be the name of the object, or its coordinates.""" # any map. This is useful for when you have major characters who will # show up in many scenes, and their inventory must stay the same, for # example. + self.singletons = {} if 'singletons' in data: for thing in data['singletons']: #thing = data['singletons'][datum] @@ -576,7 +577,7 @@ Object can be the name of the object, or its coordinates.""" self.nextThing = _gm.GameMap.addThingRecursive(i.customValues, self.nextThing + 1) thing.addThing(i) del thing.tempInventory - self.singletons = list(data['singletons']) + self.singletons[thing.name] = thing return data def gameEventLoop(self): diff --git a/gamemap.py b/gamemap.py index cb099cc..9da2f47 100644 --- a/gamemap.py +++ b/gamemap.py @@ -5,6 +5,38 @@ import ruamel.yaml import math as _mt import gamethings as _gt import gamelocus as _gl +from ruamel.yaml.comments import CommentedMap + +class Singleton(object): + """This is a super basic class (would be a struct in other languages) +that represents where a singleton Thing should be in a map.""" + yaml_flag = u'!Singleton' + + def __init__(self, name: str, x: int, y: int): + self.name = name + self.x = x + self.y = y + + @classmethod + def to_yaml(cls, representer, node): + representer.represent_mapping({ + 'name': self.name, + 'location': (self.x, self.y) + }) + + @classmethod + def from_yaml(cls, constructor, node): + parts = CommentedMap() + constructor.construct_mapping(node, parts, True) + # since all parts are necessary, I won't bother with if statements + # and let it crash. + if not isinstance(parts['name'], str): + raise RuntimeError("Name must be a string.") + if not isinstance(parts['location'], list): + raise RuntimeError("Location must be a list.") + if not (isinstance(parts['location'][0], int) and isinstance(parts['location'][1], int)): + raise RuntimeError("Coordinates must be integers.") + return cls(parts['name'], parts['location'][0], parts['location'][1]) class MapError(RuntimeError): pass @@ -24,6 +56,7 @@ class GameMap(object): yaml.register_class(_gt.Door) yaml.register_class(_gt.MapExit) yaml.register_class(_gt.MapEntrance) + yaml.register_class(Singleton) def __init__(self, name, graph, matrix, dimensions): self.name = name @@ -63,7 +96,7 @@ class GameMap(object): return text.replace('\n', end) @staticmethod - def read(infile = None, prevMap = None, preLoaded = False, nextThing = 0): + def read(infile = None, prevMap = None, singletons = None, preLoaded = False, nextThing = 0): """Read map data and return a Map object. If infile is not provided, then 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.""" @@ -106,7 +139,7 @@ Entering a map through stdin will be obsolete once testing is over.""" level = GameMap(infile, mapGraph, mapMatrix, dimensions) # Now, load other info - nextThing = GameMap.loadThings(level, info, prevMap, preLoaded, nextThing) + nextThing = GameMap.loadThings(level, info, prevMap, singletons, preLoaded, nextThing) return level, nextThing @@ -171,7 +204,7 @@ list of lists of tuples.""" return mat, graph, dim @staticmethod - def loadThings(level, info, prevMap = None, preLoaded = False, nextThing = 0): + def loadThings(level, info, prevMap = None, singletons = None, preLoaded = False, nextThing = 0): """load the things from the xml part of the map file.""" if 'openingText' in info: level.openingText = info['openingText'] @@ -197,37 +230,50 @@ list of lists of tuples.""" if 'loadOnce' in info and not preLoaded: for thing in info['loadOnce']: #print(type(thing)) - nextThing = level.addThing(thing, nextThing, True) + if isinstance(thing, _gt.Thing): + nextThing = level.addThing(thing, nextThing, True) + else: + raise MapError("Non-thing loaded as a thing:\n{}".format(thing)) if 'loadAlways' in info: for thing in info['loadAlways']: #print(type(thing)) - nextThing = level.addThing(thing, nextThing) - if ((thing.thingType == 'x' and not hasKnownEntrance) or thing.thingType == 'a') and prevMap == thing.exitid: - level.playerStart = (thing.x, thing.y) - hasKnownEntrance = True + if isinstance(thing, _gt.Thing): + nextThing = level.addThing(thing, nextThing) + if ((thing.thingType == 'x' and not hasKnownEntrance) or thing.thingType == 'a') and prevMap == thing.exitid: + level.playerStart = (thing.x, thing.y) + hasKnownEntrance = True + elif isinstance(thing, Singleton): + if singletons != None: + single = singletons[thing.name] + single.x, single.y = thing.x, thing.y + single.prevx, single.prevy = thing.x, thing.y + nextThing = level.addThing(single, nextThing) + else: + raise MapError("Non-thing loaded as a thing:\n{}".format(thing)) return nextThing # stuff the gameshell itself might use def addThing(self, thing, nextThing = 0, persist = False): - if thing == None: + if thing == None: # it must be a singleton return nextThing #if thing.name in self.thingNames: # resolved # raise ValueError("Cannot have two objects named {0}.".format(thing.name)) if thing.thingID == -1: # This is to ensure that we don't double up IDs. thing.thingID = nextThing nextThing += 1 - # Some things, like containers, have other things as custom values, - # so they need IDs as well. - if thing.thingType in 'iun': - nextThing = GameMap.addThingRecursive(thing.customValues, nextThing) - if thing.thingType == 'n': - for i in thing.tempInventory: - if i.thingID == -1: - i.thingID = nextThing - nextThing = GameMap.addThingRecursive(i.customValues, nextThing + 1) - thing.addThing(i) - del thing.tempInventory + # Some things, like containers, have other things as custom values, + # so they need IDs as well. + # Let's only add them if they weren't already loaded. + if thing.thingType in 'iun': + nextThing = GameMap.addThingRecursive(thing.customValues, nextThing) + if thing.thingType == 'n': + for i in thing.tempInventory: + if i.thingID == -1: + i.thingID = nextThing + nextThing = GameMap.addThingRecursive(i.customValues, nextThing + 1) + thing.addThing(i) + del thing.tempInventory pos = self.coordsToInt(thing.x, thing.y) if pos not in self.thingPos: self.thingPos[pos] = [thing.thingID] diff --git a/testing/test1.yml b/testing/test1.yml index 0fa649f..6c8fdaa 100644 --- a/testing/test1.yml +++ b/testing/test1.yml @@ -51,15 +51,6 @@ loadAlways: none: none # might this work to prevent this character from doing anything? customValues: dialogs: testing/testDialog.yml - - !NPC + - !Singleton name: follower - description: a follower location: [6, 26] - behaviors: - go: [-1, follow] - arrive: [-1, follow] - customValues: - follow: - distance: 2 - isFollowing: True - target: You # yes, YOU!