From c051d15f5cf015d675cb697c8a137539715e3c3b Mon Sep 17 00:00:00 2001 From: Griatch Date: Sun, 6 Sep 2020 13:09:43 +0200 Subject: [PATCH] Update prototype example module --- evennia/game_template/world/prototypes.py | 73 ++++++++++++-------- evennia/prototypes/prototypes.py | 2 +- evennia/utils/evmore.py | 81 ++++++++++++++++------- 3 files changed, 104 insertions(+), 52 deletions(-) diff --git a/evennia/game_template/world/prototypes.py b/evennia/game_template/world/prototypes.py index b64dd1b135..04aba091f3 100644 --- a/evennia/game_template/world/prototypes.py +++ b/evennia/game_template/world/prototypes.py @@ -2,40 +2,56 @@ Prototypes A prototype is a simple way to create individualized instances of a -given `Typeclass`. For example, you might have a Sword typeclass that -implements everything a Sword would need to do. The only difference -between different individual Swords would be their key, description -and some Attributes. The Prototype system allows to create a range of -such Swords with only minor variations. Prototypes can also inherit -and combine together to form entire hierarchies (such as giving all -Sabres and all Broadswords some common properties). Note that bigger -variations, such as custom commands or functionality belong in a -hierarchy of typeclasses instead. +given typeclass. It is dictionary with specific key names. -Example prototypes are read by the `@spawn` command but is also easily -available to use from code via `evennia.spawn` or `evennia.utils.spawner`. -Each prototype should be a dictionary. Use the same name as the -variable to refer to other prototypes. +For example, you might have a Sword typeclass that implements everything a +Sword would need to do. The only difference between different individual Swords +would be their key, description and some Attributes. The Prototype system +allows to create a range of such Swords with only minor variations. Prototypes +can also inherit and combine together to form entire hierarchies (such as +giving all Sabres and all Broadswords some common properties). Note that bigger +variations, such as custom commands or functionality belong in a hierarchy of +typeclasses instead. + +A prototype can either be a dictionary placed into a global variable in a +python module (a 'module-prototype') or stored in the database as a dict on a +special Script (a db-prototype). The former can be created just by adding dicts +to modules Evennia looks at for prototypes, the latter is easiest created +in-game via the `olc` command/menu. + +Prototypes are read and used to create new objects with the `spawn` command +or directly via `evennia.spawn` or the full path `evennia.prototypes.spawner.spawn`. + +A prototype dictionary have the following keywords: Possible keywords are: - prototype_parent - 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). +- `prototype_key` - the name of the prototype. This is required for db-prototypes, + for module-prototypes, the global variable name of the dict is used instead +- `prototype_parent` - string pointing to parent prototype if any. Prototype inherits + in a similar way as classes, with children overriding values in their partents. +- `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 to use for the spawned object. +- `aliases` - string or list of strings. +- `attrs` - Attributes, expressed as a list of tuples on the form `(attrname, value)`, + `(attrname, value, category)`, or `(attrname, value, category, locks)`. If using one + of the shorter forms, defaults are used for the rest. +- `tags` - Tags, as a list of tuples `(tag,)`, `(tag, category)` or `(tag, category, data)`. +- Any other keywords are interpreted as Attributes with no category or lock. + These will internally be added to `attrs` (eqivalent to `(attrname, value)`. - permissions - string or list of permission strings. - locks - a lock-string. - aliases - string or list of strings. - - ndb_ - value of a nattribute (the "ndb_" part is ignored). - any other keywords are interpreted as Attributes and their values. - -See the `@spawn` command and `evennia.utils.spawner` for more info. +See the `spawn` command and `evennia.prototypes.spawner.spawn` for more info. """ +## example of module-based prototypes using +## the variable name as `prototype_key` and +## simple Attributes + # from random import randint # # GOBLIN = { @@ -43,7 +59,8 @@ See the `@spawn` command and `evennia.utils.spawner` for more info. # "health": lambda: randint(20,30), # "resists": ["cold", "poison"], # "attacks": ["fists"], -# "weaknesses": ["fire", "light"] +# "weaknesses": ["fire", "light"], +# "tags": = [("greenskin", "monster"), ("humanoid", "monster")] # } # # GOBLIN_WIZARD = { diff --git a/evennia/prototypes/prototypes.py b/evennia/prototypes/prototypes.py index 9aea89711a..9e99a766fe 100644 --- a/evennia/prototypes/prototypes.py +++ b/evennia/prototypes/prototypes.py @@ -592,7 +592,7 @@ def validate_prototype( protparent = protparents.get(protstring) if not protparent: _flags["errors"].append( - "Prototype {}'s prototype_parent '{}' was not found.".format((protkey, protstring)) + "Prototype {}'s prototype_parent '{}' was not found.".format(protkey, protstring) ) if id(prototype) in _flags["visited"]: _flags["errors"].append( diff --git a/evennia/utils/evmore.py b/evennia/utils/evmore.py index 1db4c8d67c..9814cc6331 100644 --- a/evennia/utils/evmore.py +++ b/evennia/utils/evmore.py @@ -29,6 +29,7 @@ caller.msg() construct every time the page is updated. """ from django.conf import settings from django.db.models.query import QuerySet +from django.core.paginator import Paginator from evennia import Command, CmdSet from evennia.commands import cmdhandler from evennia.utils.utils import make_iter, inherits_from, justify @@ -131,7 +132,7 @@ class EvMore(object): def __init__( self, caller, - text, + inp, always_page=False, session=None, justify=False, @@ -143,28 +144,28 @@ class EvMore(object): ): """ - Initialization of the text handler. + Initialization of the inp handler. Args: caller (Object or Account): Entity reading the text. - text (str, EvTable or iterator): The text or data to put under paging. + inp (str, EvTable, Paginator or iterator): The text or data to put under paging. - If a string, paginage normally. If this text contains one or more `\f` format symbol, automatic pagination and justification are force-disabled and page-breaks will only happen after each `\f`. - If `EvTable`, the EvTable will be paginated with the same setting on each page if it is too long. The table decorations will be considered in the size of the page. - - Otherwise `text` is converted to an iterator, where each step is + - Otherwise `inp` is converted to an iterator, where each step is expected to be a line in the final display. Each line will be run through `iter_callable`. always_page (bool, optional): If `False`, the - pager will only kick in if `text` is too big + pager will only kick in if `inp` is too big to fit the screen. session (Session, optional): If given, this session will be used to determine the screen width and will receive all output. justify (bool, optional): If set, auto-justify long lines. This must be turned off for fixed-width or formatted output, like tables. It's force-disabled - if `text` is an EvTable. + if `inp` is an EvTable. justify_kwargs (dict, optional): Keywords for the justifiy function. Used only if `justify` is True. If this is not set, default arguments will be used. exit_on_lastpage (bool, optional): If reaching the last page without the @@ -230,31 +231,51 @@ class EvMore(object): # always limit number of chars to 10 000 per page self.height = min(10000 // max(1, self.width), height) - if inherits_from(text, "evennia.utils.evtable.EvTable"): - # an EvTable - self.init_evtable(text) - elif isinstance(text, QuerySet): - # a queryset - self.init_queryset(text) - elif not isinstance(text, str): - # anything else not a str - self.init_iterable(text) - elif "\f" in text: - # string with \f line-break markers in it - self.init_f_str(text) - else: - # a string - self.init_str(text) + # does initial parsing of input + self.parse_input(inp) # kick things into gear self.start() - # page formatter + # Hooks for customizing input handling and formatting (use if overriding this class) + + def parse_input(self, inp): + """ + Parse the input to figure out the size of the data, how many pages it + consist of and pick the correct paginator mechanism. Override this if + you want to support a new type of input. + + Each initializer should set self._paginator and optionally self._page_formatter + for properly handling the input data. + + """ + if inherits_from(inp, "evennia.utils.evtable.EvTable"): + # an EvTable + self.init_evtable(inp) + elif isinstance(inp, QuerySet): + # a queryset + self.init_queryset(inp) + elif isinstance(inp, Paginator): + self.init_django_paginator(inp) + elif not isinstance(inp, str): + # anything else not a str + self.init_iterable(inp) + elif "\f" in inp: + # string with \f line-break markers in it + self.init_f_str(inp) + else: + # a string + self.init_str(inp) def format_page(self, page): """ Page formatter. Uses the page_formatter callable by default. This allows to easier override the class if needed. + + Args: + page (any): A piece of data representing one page to display. This must + be poss + Returns: """ return self._page_formatter(page) @@ -269,7 +290,13 @@ class EvMore(object): Paginate by slice. This is done with an eye on memory efficiency (usually for querysets); to avoid fetching all objects at the same time. """ - return self._data[pageno * self.height : pageno * self.height + self.height] + return self._data[pageno * self.height: pageno * self.height + self.height] + + def paginator_django(self, pageno): + """ + Paginate using the django queryset Paginator API. Note that his is indexed from 1. + """ + return self._data.page(pageno + 1) # inits for different input types @@ -292,6 +319,14 @@ class EvMore(object): self._data = qs self._paginator = self.paginator_slice + def init_django_paginator(self, pages): + """ + The input is a django Paginator object. + """ + self._npages = pages.num_pages + self._data = pages + self._paginator = self.paginator_django + def init_iterable(self, inp): """The input is something other than a string - convert to iterable of strings""" inp = make_iter(inp)