diff --git a/CHANGELOG.md b/CHANGELOG.md index 71bc33b9bb..55ca761e56 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,10 +15,10 @@ Update to Python 3 - Add new `@force` command to have another object perform a command. - Add the Portal uptime to the `@time` command. - Make the `@link` command first make a local search before a global search. -- Have the default Unloggedin-look command look for optional `connection_screen()` callable in - `mygame/server/conf/connection_screen.py`. This allows for more flexible welcome screens - that are calculated on the fly. -- `@py` command now defaults to escaping html tags in its output when viewing in the webclient. +- Have the default Unloggedin-look command look for optional `connection_screen()` callable in + `mygame/server/conf/connection_screen.py`. This allows for more flexible welcome screens + that are calculated on the fly. +- `@py` command now defaults to escaping html tags in its output when viewing in the webclient. Use new `/clientraw` switch to get old behavior (issue #1369). ### Web @@ -73,7 +73,7 @@ Web/Django standard initiative (@strikaco) ### Prototypes -- `evennia.prototypes.save_prototype` now takes the prototype as a normal +- `evennia.prototypes.save_prototype` now takes the prototype as a normal argument (`prototype`) instead of having to give it as `**prototype`. - `evennia.prototypes.search_prototype` has a new kwarg `require_single=False` that raises a KeyError exception if query gave 0 or >1 results. @@ -106,7 +106,7 @@ Web/Django standard initiative (@strikaco) ### Server - Convert ServerConf model to store its values as a Picklefield (same as Attributes) instead of using a custom solution. -- OOB: Add support for MSDP LIST, REPORT, UNREPORT commands (re-mapped to `msdp_list`, +- OOB: Add support for MSDP LIST, REPORT, UNREPORT commands (re-mapped to `msdp_list`, `msdp_report`, `msdp_unreport` inlinefuncs_) - Added `evennia.ANSIString` to flat API. - Server/Portal log files now cycle to names on the form `server_.log_19_03_08_` instead of `server.log___19.3.8`, retaining @@ -116,6 +116,9 @@ Web/Django standard initiative (@strikaco) - `evennia` launcher now fully handles all django-admin commands, like running tests in parallel. - `evennia.utils.create.account` now also takes `tags` and `attrs` keywords. +- `evennia.utils.interactive` decorator can now allow you to use yield(secs) to pause operation + in any function, not just in Command.func. Likewise, response = yield(question) will work + if the decorated function has an argument or kwarg `caller`. - Added many more unit tests. - Swap argument order of `evennia.set_trace` to `set_trace(term_size=(140, 40), debugger='auto')` since the size is more likely to be changed on the command line. @@ -134,7 +137,7 @@ Web/Django standard initiative (@strikaco) ### Contribs -- The `extended_room` contrib saw some backwards-incompatible refactoring: +- The `extended_room` contrib saw some backwards-incompatible refactoring: + All commands now begin with `CmdExtendedRoom`. So before it was `CmdExtendedLook`, now it's `CmdExtendedRoomLook` etc. + The `detail` command was broken out of the `desc` command and is now a new, stand-alone command @@ -142,7 +145,7 @@ Web/Django standard initiative (@strikaco) command works in the tutorial-world. + The `detail` command now also supports deleting details (like the tutorial-world version). + The new `ExtendedRoomCmdSet` includes all the extended-room commands and is now the recommended way - to install the extended-room contrib. + to install the extended-room contrib. - Reworked `menu_login` contrib to use latest EvMenu standards. Now also supports guest logins. - Mail contrib was refactored to have optional Command classes `CmdMail` for OOC+IC mail (added to the CharacterCmdSet and `CmdMailCharacter` for IC-only mailing between chars (added to CharacterCmdSet) @@ -260,7 +263,7 @@ Web/Django standard initiative (@strikaco) - `tb_items` - Extends `tb_equip` with item use with conditions/status effects. - `tb_magic` - Extends `tb_equip` with spellcasting. - `tb_range` - Adds system for abstract positioning and movement. - - The `extended_room` contrib saw some backwards-incompatible refactoring: + - The `extended_room` contrib saw some backwards-incompatible refactoring: - All commands now begin with `CmdExtendedRoom`. So before it was `CmdExtendedLook`, now it's `CmdExtendedRoomLook` etc. - The `detail` command was broken out of the `desc` command and is now a new, stand-alone command @@ -268,11 +271,11 @@ Web/Django standard initiative (@strikaco) command works in the tutorial-world. - The `detail` command now also supports deleting details (like the tutorial-world version). - The new `ExtendedRoomCmdSet` includes all the extended-room commands and is now the recommended way - to install the extended-room contrib. + to install the extended-room contrib. - Updates and some cleanup of existing contribs. -### Internationalization +### Internationalization - Polish translation by user ogotai diff --git a/evennia/utils/utils.py b/evennia/utils/utils.py index e0d47690d3..263365a2a9 100644 --- a/evennia/utils/utils.py +++ b/evennia/utils/utils.py @@ -16,10 +16,11 @@ import math import re import textwrap import random -import pickle +import inspect +from twisted.internet.task import deferLater from os.path import join as osjoin from importlib import import_module -from importlib.util import find_spec, module_from_spec +from importlib.util import find_spec from inspect import ismodule, trace, getmembers, getmodule, getmro from collections import defaultdict, OrderedDict from twisted.internet import threads, reactor, task @@ -1981,3 +1982,67 @@ def get_all_typeclasses(parent=None): typeclasses = {name: typeclass for name, typeclass in typeclasses.items() if inherits_from(typeclass, parent)} return typeclasses + + +def interactive(func): + """ + Decorator to make a method pausable with yield(seconds) + and able to ask for user-input with response=yield(question). + For the question-asking to work, 'caller' must the name + of an argument or kwarg to the decorated function. + + Note that this turns the method into a generator. + + Example usage: + + @interactive + def myfunc(caller): + caller.msg("This is a test") + # wait five seconds + yield(5) + # ask user (caller) a question + response = yield("Do you want to continue waiting?") + if response == "yes": + yield(5) + else: + # ... + + """ + from evennia.utils.evmenu import get_input + def _process_input(caller, prompt, result, generator): + deferLater(reactor, 0, _iterate, generator, caller, response=result) + return False + + def _iterate(generator, caller=None, response=None): + try: + if response is None: + value = next(generator) + else: + value = generator.send(response) + except StopIteration: + pass + else: + if isinstance(value, (int, float)): + delay(value, _iterate, generator, caller=caller) + elif isinstance(value, str): + if not caller: + raise ValueError("To retrieve input from a @pausable method, that method " + "must be called with a 'caller' argument)") + get_input(caller, value, _process_input, generator=generator) + else: + raise ValueError("yield(val) in a @pausable method must have an int/float as arg.") + + def decorator(*args, **kwargs): + argnames = inspect.getfullargspec(func).args + caller = None + if 'caller' in argnames: + # we assume this is an object + caller = args[argnames.index('caller')] + + ret = func(*args, **kwargs) + if isinstance(ret, types.GeneratorType): + _iterate(ret, caller) + else: + return ret + + return decorator