diff --git a/game/gamesrc/world/examples/prototypes.py b/game/gamesrc/world/examples/prototypes.py new file mode 100644 index 0000000000..537616f103 --- /dev/null +++ b/game/gamesrc/world/examples/prototypes.py @@ -0,0 +1,56 @@ +""" +Example prototypes read by the @spawn command but is also easily +available to use from code. Each prototype should be a dictionary. Use +the same name as the variable to refer to other prototypes. + +Possible keywords are: + prototype - string pointing to parent prototype of this structure + key - string, the main object identifier + typeclass - string, if not set, will use settings.BASE_OBJECT_TYPECLASS + location - this should be a valid object or #dbref + home - valid object or #dbref + destination - only valid for exits (object or dbref) + + permissions - string or list of permission strings + locks - a lock-string + aliases - string or list of strings + + ndb_ - value of a nattribute (ndb_ is stripped) + any other keywords are interpreted as Attributes and their values. + +See the @spawn command and src.utils.spawner for more info. + +""" + +from random import randint + +NOBODY = {} + +GOBLIN = { + "key": "goblin grunt", + "health": lambda: randint(20,30), + "resists": ["cold", "poison"], + "attacks": ["fists"], + "weaknesses": ["fire", "light"] + } + +GOBLIN_WIZARD = { + "prototype": "GOBLIN", + "key": "goblin wizard", + "spells": ["fire ball", "lighting bolt"] + } + +GOBLIN_ARCHER = { + "prototype": "GOBLIN", + "key": "goblin archer", + "attacks": ["short bow"] +} + +ARCHWIZARD = { + "attacks": ["archwizard staff"], +} + +GOBLIN_ARCHWIZARD = { + "key": "goblin archwizard", + "prototype" : ("GOBLIN_WIZARD", "ARCHWIZARD") +} diff --git a/src/commands/default/building.py b/src/commands/default/building.py index 909bcd756f..cf21b03148 100644 --- a/src/commands/default/building.py +++ b/src/commands/default/building.py @@ -19,7 +19,7 @@ __all__ = ("ObjManipCommand", "CmdSetObjAlias", "CmdCopy", "CmdUnLink", "CmdSetHome", "CmdListCmdSets", "CmdName", "CmdOpen", "CmdSetAttribute", "CmdTypeclass", "CmdWipe", "CmdLock", "CmdExamine", "CmdFind", "CmdTeleport", - "CmdScript", "CmdTag") + "CmdScript", "CmdTag", "CmdSpawn") try: # used by @set @@ -30,7 +30,7 @@ except ImportError: # used by @find CHAR_TYPECLASS = settings.BASE_CHARACTER_TYPECLASS - +_PROTOTYPE_PARENTS = None class ObjManipCommand(MuxCommand): """ @@ -2244,17 +2244,33 @@ class CmdTag(MuxCommand): string = "No tags attached to %s." % obj self.caller.msg(string) +# +# To use the prototypes with the @spawn function, copy +# game/gamesrc/world/examples/prototypes.py up one level +# to game/gamesrc/world. Then add to game/settings.py the +# line +# PROTOTYPE_MODULES = ["game.gamesrc.commands.prototypes"] +# Reload the server and the prototypes should be available. +# + class CmdSpawn(MuxCommand): """ spawn objects from prototype Usage: - @spawn {prototype dictionary} + @spawn[/switches] {prototype dictionary} + + Switches: + noloc - allow location to None. Otherwise, location will default to + caller's current location + parents - show all available prototype parents Example: @spawn {"key":"goblin", "typeclass":"monster.Monster", "location":"#2"} Dictionary keys: + {wprototype {n - name of parent prototype to use. Can be a list for + multiple inheritance (inherits left to right) {wkey {n - string, the main object identifier {wtypeclass {n - string, if not set, will use settings.BASE_OBJECT_TYPECLASS {wlocation {n - this should be a valid object or #dbref @@ -2266,7 +2282,8 @@ class CmdSpawn(MuxCommand): {wndb_{n - value of a nattribute (ndb_ is stripped) any other keywords are interpreted as Attributes and their values. - This command can't access prototype inheritance. + The parent prototypes are taken as dictionaries defined globally in + the settings.PROTOTYPE_MODULES. """ key = "@spawn" @@ -2274,9 +2291,22 @@ class CmdSpawn(MuxCommand): help_category = "Building" def func(self): - "Implements the spawn" + "Implements the spawner" + + global _PROTOTYPE_PARENTS + if _PROTOTYPE_PARENTS is None: + if hasattr(settings, "PROTOTYPE_MODULES"): + # read prototype parents from setting + _PROTOTYPE_PARENTS = {} + for prototype_module in utils.make_iter(settings.PROTOTYPE_MODULES): + _PROTOTYPE_PARENTS.update(dict((key, val) + for key, val in utils.all_from_module(prototype_module).items() if isinstance(val, dict))) + if not self.args: - self.caller.msg("Usage: @spawn {key:value, key, value, ...}") + string = "Usage: @spawn {key:value, key, value, ...}\n" \ + "Available prototypes: %s" + self.caller.msg(string % ", ".join(_PROTOTYPE_PARENTS.keys()) + if _PROTOTYPE_PARENTS else None) return from src.utils.spawner import spawn @@ -2297,7 +2327,10 @@ class CmdSpawn(MuxCommand): self.caller.msg("The prototype must be a Python dictionary.") return - for obj in spawn(prototype): + if not "noloc" in self.switches and not "location" in prototype: + prototype["location"] = self.caller.location + + for obj in spawn(prototype, prototype_parents=_PROTOTYPE_PARENTS): self.caller.msg("Spawned %s." % obj.key) diff --git a/src/utils/spawner.py b/src/utils/spawner.py index ba3005e09a..09a63b645c 100644 --- a/src/utils/spawner.py +++ b/src/utils/spawner.py @@ -18,7 +18,7 @@ GOBLIN = { } Possible keywords are: - prototype - dict, parent prototype of this structure (see below) + prototype - string parent prototype key - string, the main object identifier typeclass - string, if not set, will use settings.BASE_OBJECT_TYPECLASS location - this should be a valid object or #dbref @@ -83,16 +83,16 @@ _CREATE_OBJECT_KWARGS = ("key", "location", "home", "destination") _handle_dbref = lambda inp: handle_dbref(inp, ObjectDB) -def _get_prototype(dic, prot): +def _get_prototype(dic, prot, protparents): "Recursively traverse a prototype dictionary, including multiple inheritance" if "prototype" in dic: # move backwards through the inheritance prototypes = dic["prototype"] - if isinstance(prototypes, dict): + if not hasattr(prototypes, "__iter__"): prototypes = (prototypes,) for prototype in prototypes: # Build the prot dictionary in reverse order, overloading - new_prot = _get_prototype(prototype, prot) + new_prot = _get_prototype(protparents.get(prototype, {}), prot, protparents) prot.update(new_prot) prot.update(dic) prot.pop("prototype", None) # we don't need this anymore @@ -150,16 +150,21 @@ def _batch_create_object(*objparams): return objs -def spawn(*prototypes): +def spawn(*prototypes, **kwargs): """ Spawn a number of prototyped objects. Each argument should be a prototype dictionary. + + The keyword argument "prototype_parents" holds a dictionary of + prototype dictionaries, each with a unique key. The given + prototypes may call these as parents using the "prototype" key. """ objsparams = [] for prototype in prototypes: + protparents = kwargs.get("prototype_parents", None) - prot = _get_prototype(prototype, {}) + prot = _get_prototype(prototype, {}, protparents) if not prot: continue @@ -195,35 +200,32 @@ def spawn(*prototypes): if __name__ == "__main__": # testing - NOBODY = {} - - GOBLIN = { - "key": "goblin grunt", - "health": lambda: randint(20,30), - "resists": ["cold", "poison"], - "attacks": ["fists"], - "weaknesses": ["fire", "light"] - } - - GOBLIN_WIZARD = { - "prototype": GOBLIN, - "key": "goblin wizard", - "spells": ["fire ball", "lighting bolt"] - } - - GOBLIN_ARCHER = { - "prototype": GOBLIN, - "key": "goblin archer", - "attacks": ["short bow"] - } - - ARCHWIZARD = { - "attacks": ["archwizard staff"], - } - - GOBLIN_ARCHWIZARD = { - "key": "goblin archwizard", - "prototype" : (GOBLIN_WIZARD, ARCHWIZARD) - } + protparents = { + "NOBODY": {}, + "GOBLIN" : { + "key": "goblin grunt", + "health": lambda: randint(20,30), + "resists": ["cold", "poison"], + "attacks": ["fists"], + "weaknesses": ["fire", "light"] + }, + "GOBLIN_WIZARD" : { + "prototype": "GOBLIN", + "key": "goblin wizard", + "spells": ["fire ball", "lighting bolt"] + }, + "GOBLIN_ARCHER" : { + "prototype": "GOBLIN", + "key": "goblin archer", + "attacks": ["short bow"] + }, + "ARCHWIZARD" : { + "attacks": ["archwizard staff"], + }, + "GOBLIN_ARCHWIZARD" : { + "key": "goblin archwizard", + "prototype" : ("GOBLIN_WIZARD", "ARCHWIZARD") + } + } # test - print [o.key for o in spawn(GOBLIN, GOBLIN_ARCHWIZARD)] + print [o.key for o in spawn(protparents["GOBLIN"], protparents["GOBLIN_ARCHWIZARD"], prototype_parents=protparents)] diff --git a/src/utils/utils.py b/src/utils/utils.py index 5d640751d7..24474b1676 100644 --- a/src/utils/utils.py +++ b/src/utils/utils.py @@ -792,6 +792,8 @@ def all_from_module(module): Return all global-level variables from a module as a dict """ mod = mod_import(module) + if not mod: + return {} return dict((key, val) for key, val in mod.__dict__.items() if not (key.startswith("_") or ismodule(val)))