Work on cleaning up docs

This commit is contained in:
Griatch 2023-05-19 11:11:46 +02:00
parent 1c5746d59c
commit 40023923e1
17 changed files with 218 additions and 456 deletions

View file

@ -13,7 +13,9 @@ SPHINXAPIDOC ?= sphinx-apidoc
SPHINXAPIDOCOPTS = --tocfile evennia-api --module-first --force -d 6 --separate --templatedir=$(SOURCEDIR)/_templates/
SPHINXAPIDOCOPTSQUICK = --tocfile evennia-api --module-first -d 6 --separate --templatedir=$(SOURCEDIR)/_templates/
SPHINXAPIDOCENV = members,undoc-members,show-inheritance
SPHINXAPIDOCEXCLUDE = ../*/migrations/* ../evennia/game_template/* ../evennia/*/tests/* ../evennia/*/tests.py
SPHINXAPIDOCEXCLUDE = ../*/migrations/* ../evennia/game_template/* ../evennia/*/tests.py ../evennia/accounts/tests/* ../evennia/commands/tests/* ../evennia/comms/tests/* ../evennia/help/tests/* ../evennia/locks/tests/* ../evennia/objects/tests/* ../evennia/prototypes/tests/* ../evennia/scripts/tests/* ../evennia/server/tests/* ../evennia/typeclasses/tests/* ../evennia/utils/tests/* ../evennia/web/tests/* # don't exclude contrib tests for tutorial purposes
# ../evennia/*/tests/* ../evennia/*/tests.py
EVDIR ?= $(realpath ../evennia)
EVGAMEDIR ?= $(realpath ../../gamedir)

View file

@ -1,279 +0,0 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
Copy data from old Evennia github Wiki to static files.
Prepare files for mkdoc. This assumes evennia.wiki is cloned
to a folder at the same level as the evennia repo.
Just run this to update everything.
We also need to build the toc-tree and should do so automatically for now.
"""
import datetime
import glob
import re
_RE_MD_LINK = re.compile(r"\[(?P<txt>[\w -\[\]]+?)\]\((?P<url>.+?)\)", re.I + re.S + re.U)
_RE_REF_LINK = re.compile(r"\[[\w -\[\]]*?\]\(.+?\)", re.I + re.S + re.U)
_RE_CLEAN = re.compile(r"\|-+?|-+\|", re.I + re.S + re.U)
_IGNORE_FILES = (
"_Sidebar.md",
# "Wiki-Index.md"
)
_INDEX_PREFIX = f"""
# VERSION WARNING
> This is the experimental static v0.9 documentation of Evennia, _automatically_ generated from the
> [evennia wiki](https://github.com/evennia/evennia/wiki/) at {datetime.datetime.now()}.
> There are known conversion issues which will _not_ be addressed in this version - refer to
> the original wiki if you have trouble.
>
> Manual conversion and cleanup will instead happen during development of the upcoming v1.0
> version of this static documentation.
"""
_WIKI_DIR = "../../../evennia.wiki/"
_INFILES = [
path
for path in sorted(glob.glob(_WIKI_DIR + "/*.md"))
if path.rsplit("/", 1)[-1] not in _IGNORE_FILES
]
_FILENAMES = [path.rsplit("/", 1)[-1] for path in _INFILES]
_FILENAMES = [path.split(".", 1)[0] for path in _FILENAMES]
_FILENAMESLOW = [path.lower() for path in _FILENAMES]
_OUTDIR = "../source/"
_OLD_WIKI_URL = "https://github.com/evennia/evennia/wiki/"
_OLD_WIKI_URL_LEN = len(_OLD_WIKI_URL)
_CODE_PREFIX = "github:"
_API_PREFIX = "api:"
_CUSTOM_LINK_REMAP = {
"CmdSets": "Command-Sets",
"CmdSet": "Command-Sets",
"Cmdsets": "Command-Sets",
"CommandSet": "Command-Sets",
"batch-code-processor": "Batch-Code-Processor",
"Batch-code-processor": "Batch-Code-Processor",
"batch-command-processor": "Batch-Command-Processor",
"Batch-command-processor": "Batch-Command-Processor",
"evennia-API": "Evennia-API",
"Channels": "Communications#Channels",
"Comms": "Communications",
"typeclass": "Typeclasses",
"Home": "index",
"Help-system": "Help-System",
"Using-Mux-as-a-Standard": "Using-MUX-as-a-Standard",
"Building-quickstart": "Building-Quickstart",
"Adding-Object-Typeclass-tutorial": "Adding-Object-Typeclass-Tutorial",
"EvTable": _API_PREFIX + "evennia.utils#module-evennia.utils.evtable",
}
# complete reference remaps
_REF_REMAP = {
"[![Getting Started][icon_new]](Getting-Started)": "![Getting Started][icon_new]",
"[![Admin Docs][icon_admin]](Administrative-Docs)": "![Admin Docs][icon_admin]",
"[![Builder Docs][icon_builder]](Builder-Docs)": "![Builder Docs][icon_builder]",
"[![Developer-Central][icon_devel]](Developer-Central)": "![Developer-Central][icon_devel]",
"[![tutorial][icon_tutorial]](Tutorials)": "![Tutorials][icon_tutorial]",
"[![API][icon_api]](evennia)": "![API][icon_api]",
"[](Wiki-front-page.)": "",
}
# absolute links (mainly github links) that should not be converted. This
# should be given without any #anchor.
_ABSOLUTE_LINK_SKIP = (
# "https://github.com/evennia/evennia/wiki/feature-request",
)
# specific references tokens that should be ignored. Should be given
# without any #anchor.
_REF_SKIP = (
"[5](Win)",
"[6](Win)",
"[7](Win)",
"[10](Win)",
"[11](Mac)",
"[13](Win)",
"[14](IOS)",
"[15](IOS)",
"[16](Andr)",
"[17](Andr)",
"[18](Unix)",
"[21](Chrome)",
# these should be checked
"[EvTable](EvTable)",
"[styled](OptionStyles)",
"[Inputfunc](Inputfunc)",
"[online documentation wiki](index)",
"[online documentation](index)",
"[Accounts](Account)",
"[Session](Session)",
"[Inputfuncs](Inputfunc)",
)
_CURRENT_TITLE = ""
def _sub_remap(match):
"""Total remaps"""
ref = match.group(0)
if ref in _REF_REMAP:
new_ref = _REF_REMAP[ref]
print(f" Replacing reference {ref} -> {new_ref}")
return new_ref
return ref
def _sub_link(match):
mdict = match.groupdict()
txt, url_orig = mdict["txt"], mdict["url"]
url = url_orig
# if not txt:
# # the 'comment' is not supported by Mkdocs
# return ""
print(f" [{txt}]({url})")
url = _CUSTOM_LINK_REMAP.get(url, url)
url, *anchor = url.rsplit("#", 1)
if url in _ABSOLUTE_LINK_SKIP:
url += ("#" + anchor[0]) if anchor else ""
return f"[{txt}]({url})"
if url.startswith("evennia"):
print(f" Convert evennia url {url} -> {_CODE_PREFIX + url}")
url = _API_PREFIX + url
if url.startswith(_OLD_WIKI_URL):
# old wiki is an url on the form https://<wikiurl>/wiki/TextTags#header
# we don't refer to the old wiki but use internal mapping.
if len(url) != len(_OLD_WIKI_URL):
url_conv = url[_OLD_WIKI_URL_LEN:]
url_conv = re.sub(r"%20", "-", url_conv)
if url_conv.endswith("/_edit"):
# this is actually a bug in the wiki format
url_conv = url_conv[:-6]
if url_conv.startswith("evennia"):
# this is an api link
url_conv = _CODE_PREFIX + url_conv
print(f" Converting wiki-url: {url} -> {url_conv}")
url = url_conv
if not url and anchor:
# this happens on same-file #labels in wiki
url = _CURRENT_TITLE
if url not in _FILENAMES and not url.startswith("http") and not url.startswith(_CODE_PREFIX):
url_cap = url.capitalize()
url_plur = url[:-3] + "s" + ".md"
url_cap_plur = url_plur.capitalize()
link = f"[{txt}]({url})"
if link in _REF_SKIP:
url = link
elif url_cap in _FILENAMES:
print(f" Replacing (capitalized): {url.capitalize()}")
url = url_cap
elif url_plur in _FILENAMES:
print(f" Replacing (pluralized): {url + 's'}")
url = url_plur
elif url_cap_plur in _FILENAMES:
print(f" Replacing (capitalized, pluralized): {url.capitalize() + 's'}")
url = url_cap_plur
elif url.lower() in _FILENAMESLOW:
ind = _FILENAMESLOW.index(url.lower())
alt = _FILENAMES[ind]
print(f" Replacing {url} with different cap: {alt}")
url = alt
# print(f"\nlink {link} (orig: [{txt}]({url_orig})) found no file match")
# inp = input("Enter alternate url (return to keep old): ")
# if inp.strip():
# url = inp.strip()
if anchor:
url += "#" + anchor[0]
return f"[{txt}]({url})"
def create_toctree(files):
with open("../source/toc.md", "w") as fil:
fil.write("# Toc\n")
for path in files:
filename = path.rsplit("/", 1)[-1]
ref = filename.rsplit(".", 1)[0]
linkname = ref.replace("-", " ")
if ref == "Home":
ref = "index"
fil.write(f"\n* [{linkname}]({ref}.md)")
def convert_links(files, outdir):
global _CURRENT_TITLE
for inpath in files:
is_index = False
outfile = inpath.rsplit("/", 1)[-1]
if outfile == "Home.md":
outfile = "index.md"
is_index = True
outfile = _OUTDIR + outfile
title = inpath.rsplit("/", 1)[-1].split(".", 1)[0].replace("-", " ")
print(f"Converting links in {inpath} -> {outfile} ...")
with open(inpath) as fil:
text = fil.read()
if is_index:
text = _INDEX_PREFIX + text
lines = text.split("\n")
lines = (
lines[:-11]
+ [" - The [TOC](toc) lists all regular documentation pages.\n\n"]
+ lines[-11:]
)
text = "\n".join(lines)
_CURRENT_TITLE = title.replace(" ", "-")
text = _RE_CLEAN.sub("", text)
text = _RE_REF_LINK.sub(_sub_remap, text)
text = _RE_MD_LINK.sub(_sub_link, text)
text = (
text.split("\n")[1:]
if text.split("\n")[0].strip().startswith("[]")
else text.split("\n")
)
text = "\n".join(text)
if not is_index:
text = f"# {title}\n\n{text}"
with open(outfile, "w") as fil:
fil.write(text)
if __name__ == "__main__":
print("This should not be run on develop files, it would overwrite changes.")
# create_toctree(_INFILES)
# convert_links(_INFILES, _OUTDIR)

View file

@ -1,19 +1,18 @@
# EvAdventure
Contrib by Griatch 2022
Contrib by Griatch 2023-
```{warning}
NOTE - this tutorial is WIP and NOT complete! It was put on hold to focus on
releasing Evennia 1.0. You will still learn things from it, but don't expect
perfection.
NOTE - this tutorial is WIP and NOT complete yet! You will still learn
things from it, but don't expect perfection.
```
A complete example MUD using Evennia. This is the final result of what is
implemented if you follow the Getting-Started tutorial. It's recommended
that you follow the tutorial step by step and write your own code. But if
you prefer you can also pick apart or use this as a starting point for your
own game.
implemented if you follow [Part 3 of the Getting-Started tutorial](../Howtos/Beginner-Tutorial/Part3/Beginner-Tutorial-Part3-Overview.md).
It's recommended that you follow the tutorial step by step and write your own
code. But if you prefer you can also pick apart or use this as a starting point
for your own game.
## Features

View file

@ -674,13 +674,12 @@ character make small verbal observations at irregular intervals.
### `evadventure`
_Contrib by Griatch 2022_
_Contrib by Griatch 2023-_
```{warning}
NOTE - this tutorial is WIP and NOT complete! It was put on hold to focus on
releasing Evennia 1.0. You will still learn things from it, but don't expect
perfection.
NOTE - this tutorial is WIP and NOT complete yet! You will still learn
things from it, but don't expect perfection.
```
[Read the documentation](./Contrib-Evadventure.md) - [Browse the Code](evennia.contrib.tutorials.evadventure)

View file

@ -1,11 +1,11 @@
# Combat base framework
Combat is core to many games. Exactly how it works is very game-dependent. For EvAdventure we will show off two common flavors:
Combat is core to many games. Exactly how it works is very game-dependent. In this lesson we will build a framework to implement two common flavors:
- "Twitch-based" combat means that you perform a combat action by entering a command, and after some delay (which may depend on your skills etc), the action happens. It's called 'twitch' because actions often happen fast enough that changing your strategy may involve some element of quick thinking and a 'twitchy trigger finger'.
- "Turn-based" combat means that players input actions in clear turns. Timeout for entering/queuing your actions is often much longer than twitch-based style. Once everyone made their choice (or the timeout is reached), everyone's action happens all at once, after which the next turn starts. This style of combat requires less reflexes.
- "Twitch-based" combat ([specific lesson here](./Beginner-Tutorial-Combat-Twitch.md)) means that you perform a combat action by entering a command, and after some delay (which may depend on your skills etc), the action happens. It's called 'twitch' because actions often happen fast enough that changing your strategy may involve some element of quick thinking and a 'twitchy trigger finger'.
- "Turn-based" combat ([specific lesson here](./Beginner-Tutorial-Combat-Turnbased.md)) means that players input actions in clear turns. Timeout for entering/queuing your actions is often much longer than twitch-based style. Once everyone made their choice (or the timeout is reached), everyone's action happens all at once, after which the next turn starts. This style of combat requires less player reflexes.
We will design a common combat system that supports both styles.
We will design a base combat system that supports both styles.
- We need a `CombatHandler` to track the progress of combat. This will be a [Script](../../../Components/Scripts.md). Exactly how this works (and where it is stored) will be a bit different between Twitch- and Turnbased combat. We will create its common framework in this lesson.
- Combat are divided into _actions_. We want to be able to easily extend our combat with more possible actions. An action needs Python code to show what actually happens when the action is performed. We will define such code in `Action` classes.
@ -20,9 +20,9 @@ In [evennia/contrib/tutorials/evadventure/combat_base.py](evennia.contrib.tutori
```
Our "Combat Handler" will handle the administration around combat. It needs to be _persistent_ (even is we reload the server your combat should keep going).
Creating the CombatHandler is a little of a catch-22 - how it works depends on how actions and action-dicts look. But without having the CombatHandler, it's hard to know how to design Actions and Action-dicts. So we'll start with its general structure and fill out the details later in this lesson.
Creating the CombatHandler is a little of a catch-22 - how it works depends on how Actions and Action-dicts look. But without having the CombatHandler, it's hard to know how to design Actions and Action-dicts. So we'll start with its general structure and fill out the details later in this lesson.
Below, methods with `pass` will be filled out this lesson while those raising `NotImplementedError` will be different for Twitch/Turnbased combat and will be implemented in their respective lessons.
Below, methods with `pass` will be filled out this lesson while those raising `NotImplementedError` will be different for Twitch/Turnbased combat and will be implemented in their respective lessons following this one.
```python
# in evadventure/combat_base.py
@ -41,15 +41,15 @@ class EvAdventureCombatBaseHandler(DefaultSCript):
and tracks all sides of it.
"""
# common for all types of combat
# common for all types of combat
action_classes = {} # to fill in later
fallback_action_dict = {}
@classmethod
def get_or_create_combathandler(cls, obj, **kwargs):
""" Get or create combathandler on `obj`."""
pass
@classmethod
def get_or_create_combathandler(cls, obj, **kwargs):
""" Get or create combathandler on `obj`."""
pass
def msg(self, message, combatant=None, broadcast=True, location=True):
"""
@ -58,13 +58,13 @@ class EvAdventureCombatBaseHandler(DefaultSCript):
"""
pass # TODO
def get_combat_summary(self, combatant):
"""
Get a nicely formatted 'battle report' of combat, from the
perspective of the combatant.
"""
pass # TODO
def get_combat_summary(self, combatant):
"""
Get a nicely formatted 'battle report' of combat, from the
perspective of the combatant.
"""
pass # TODO
# implemented differently by Twitch- and Turnbased combat
@ -75,28 +75,28 @@ class EvAdventureCombatBaseHandler(DefaultSCript):
(who is _not_ included in the `allies` list.
"""
raise NotImplementedError
raise NotImplementedError
def give_advantage(self, recipient, target):
"""
Give advantage to recipient against target.
"""
raise NotImplementedError
raise NotImplementedError
def give_disadvantage(self, recipient, target):
"""
Give disadvantage to recipient against target.
"""
"""
raise NotImplementedError
def has_advantage(self, combatant, target):
"""
Does combatant have advantage against target?
"""
raise NotImplementedError
def has_advantage(self, combatant, target):
"""
Does combatant have advantage against target?
"""
raise NotImplementedError
def has_disadvantage(self, combatant, target):
"""
@ -105,13 +105,13 @@ class EvAdventureCombatBaseHandler(DefaultSCript):
"""
raise NotImplementedError
def queue_action(self, combatant, action_dict):
"""
Queue an action for the combatant by providing
an action dict.
"""
raise NotImplementedError
def queue_action(self, combatant, action_dict):
"""
Queue an action for the combatant by providing
action dict.
"""
raise NotImplementedError
def execute_next_action(self, combatant):
"""
@ -120,26 +120,26 @@ class EvAdventureCombatBaseHandler(DefaultSCript):
"""
raise NotImplementedError
def start_combat(self):
"""
Start combat.
"""
raise NotImplementedError
def check_stop_combat(self):
"""
Check if the combat is over and if it should be stopped.
"""
raise NotImplementedError
def stop_combat(self):
"""
Stop combat and do cleanup.
"""
raise NotImplementedError
def start_combat(self):
"""
Start combat.
"""
raise NotImplementedError
def check_stop_combat(self):
"""
Check if the combat is over and if it should be stopped.
"""
raise NotImplementedError
def stop_combat(self):
"""
Stop combat and do cleanup.
"""
raise NotImplementedError
```
@ -163,7 +163,7 @@ from evennia import create_script
class EvAdventureCombatBaseHandler(DefaultScript):
# ...
# ...
@classmethod
def get_or_create_combathandler(cls, obj, **kwargs):
@ -204,9 +204,11 @@ class EvAdventureCombatBaseHandler(DefaultScript):
This helper method uses `obj.scripts.get()` to find if the combat script already exists 'on' the provided `obj`. If not, it will create it using Evennia's [create_script](evennia.utils.create.create_script) function. For some extra speed we cache the handler as `obj.ndb.combathandler` The `.ndb.` (non-db) means that handler is cached only in memory.
To know if the cache is out of date, we make sure to also check if the combathandler we got has an `id` that is not `None` . If it's `None`, this means the database entity was deleted and we just got its cached python representation from memory - we need to recreate it.
```{sidebar} Checking .id (or .pk)
When getting it from cache, we make sure to also check if the combathandler we got has a database `.id` that is not `None` (we could also check `.pk`, stands for "primary key") . If it's `None`, this means the database entity was deleted and we just got its cached python representation from memory - we need to recreate it.
```
This is a `classmethod`, meaning it should be used on the handler class directly (rather than on an _instance_ of said class). This makes sense because this method actually should return the new instance.
`get_or_create_combathandler` is decorated to be a [classmethod](https://docs.python.org/3/library/functions.html#classmethod), meaning it should be used on the handler class directly (rather than on an _instance_ of said class). This makes sense because this method actually should return the new instance.
As a class method we'll need to call this directly on the class, like this:
@ -392,7 +394,7 @@ In EvAdventure we will only support a few common combat actions, mapping to the
To pass around the details of an attack (the second point above), we will use a `dict`. A `dict` is simple and also easy to save in an `Attribute`. We'll call this the `action_dict` and here's what we need for each action.
> You don't need to type these out anywhere, we will use them when calling `combathandler.queue_action(combatant, action_dict)`.
> You don't need to type these out anywhere, it's listed here for reference. We will use these dicts when calling `combathandler.queue_action(combatant, action_dict)`.
```python
hold_action_dict = {
@ -408,7 +410,8 @@ stunt_action_dict = {
"target": <Character/NPC>, # who the recipient gainst adv/dis against
"advantage": bool, # grant advantage or disadvantage?
"stunt_type": Ability, # Ability to use for the challenge
"defense_type": Ability, # what Ability for recipient to defend against dis
"defense_type": Ability, # what Ability for recipient to defend with if we
# are trying to give disadvantage
}
use_item_action_dict = {
"key": "use",
@ -419,6 +422,11 @@ wield_action_dict = {
"key": "wield",
"item": <Object>
}
# used only for the turnbased combat, so its Action will be defined there
flee_action_dict = {
"key": "flee"
}
```
Apart from the `stunt` action, these dicts are all pretty simple. The `key` identifes the action to perform and the other fields identifies the minimum things you need to know in order to resolve each action.
@ -432,7 +440,7 @@ attack_action_dict = {
}
```
Let's make the `Stunt` dict a little clearer too. In this example, The `Trickster` is performing a _Stunt_ in order to help his friend `Paladin` to gain an INT- _advantage_ against the `Goblin` (maybe the paladin is preparing to cast a spell of something). Since `Trickster` is doing the action, he's not showing up in the dict:
Let's explain the longest action dict, the `Stunt` action dict in more detail as well. In this example, The `Trickster` is performing a _Stunt_ in order to help his friend `Paladin` to gain an INT- _advantage_ against the `Goblin` (maybe the paladin is preparing to cast a spell of something). Since `Trickster` is doing the action, he's not showing up in the dict:
```python
stunt_action_dict - {
@ -440,11 +448,15 @@ stunt_action_dict - {
"recipient": Paladin,
"target": Goblin,
"advantage": True,
"stunt_type": Ability.DEX,
"stunt_type": Ability.INT,
"defense_type": Ability.INT,
}
```
This should result in a DEX vs INT based check between the `Trickster` and the `Goblin` (maybe the trickster is trying to trick the goblin with some sleight-of-hand?). If the `Trickster` wins, the `Paladin` gains advantage against the Goblin on the `Paladin`'s next action.
```{sidebar}
In EvAdventure, we'll always set `stunt_type == defense_type` for simplicity. But you could also consider mixing things up so you could use DEX to confuse someone and give them INT disadvantage, for example.
```
This should result in an INT vs INT based check between the `Trickster` and the `Goblin` (maybe the trickster is trying to confuse the goblin with some clever word play). If the `Trickster` wins, the `Paladin` gains advantage against the Goblin on the `Paladin`'s next action .
## Action classes
@ -509,6 +521,8 @@ if action.can_use():
```python
# in evadventure/combat_base.py
# ...
class CombatActionHold(CombatAction):
"""
Action that does nothing
@ -527,6 +541,8 @@ Holding does nothing but it's cleaner to nevertheless have a separate class for
```python
# in evadventure/combat_base.py
# ...
class CombatActionAttack(CombatAction):
"""
A regular attack, using a wielded weapon.
@ -555,6 +571,10 @@ Refer to how we [designed Evadventure weapons](./Beginner-Tutorial-Objects.md#we
### Stunt Action
```python
# in evadventure/combat_base.py
# ...
class CombatActionStunt(CombatAction):
"""
Perform a stunt the grants a beneficiary (can be self) advantage on their next action against a
@ -622,8 +642,6 @@ class CombatActionStunt(CombatAction):
"|yHaving succeeded, you hold back to plan your next move.|n [hold]",
broadcast=False,
)
combathandler.queue_action(
attacker, combathandler.fallback_action_dict)
else:
self.msg(f"$You({defender.key}) $conj(resist)! $You() $conj(fail) the stunt.")
@ -638,6 +656,10 @@ After we have performed a successful stunt, we queue the `combathandler.fallback
### Use Item Action
```python
# in evadventure/combat_base.py
# ...
class CombatActionUseItem(CombatAction):
"""
Use an item in combat. This is meant for one-off or limited-use items (so things like scrolls and potions, not swords and shields). If this is some sort of weapon or spell rune, we refer to the item to determine what to use for attack/defense rolls.
@ -663,11 +685,6 @@ class CombatActionUseItem(CombatAction):
disadvantage=self.combathandler.has_disadvantage(user, target),
)
item.at_post_use(user, target)
# to back to idle after this
self.combathandler.queue_action(
self.combatant, i
self.combathandler.fallback_action_dict
)
```
See the [Consumable items in the Object lesson](./Beginner-Tutorial-Objects.md) to see how consumables work. Like with weapons, we offload all the logic to the item we use.
@ -675,6 +692,10 @@ See the [Consumable items in the Object lesson](./Beginner-Tutorial-Objects.md)
### Wield Action
```python
# in evadventure/combat_base.py
# ...
class CombatActionWield(CombatAction):
"""
Wield a new weapon (or spell) from your inventory. This will
@ -689,9 +710,6 @@ class CombatActionWield(CombatAction):
def execute(self):
self.combatant.equipment.move(self.item)
self.combathandler.queue_action(
self.combatant, self.combathandler.fallback_action_dict
)
```

View file

@ -1203,9 +1203,12 @@ Our turnbased combat system is complete!
## Testing
```{sidebar}
See [evennia/contrib/tutorials/evadventure/tests/test_combat.py](evennia.contrib.tutorials.evadventure.tests.test_combat)
```
Unit testing of the Turnbased combat handler is straight forward, you follow the process of earlier lessons to test that each method on the handler returns what you expect with mocked inputs.
Unit-testing the menu is more complex. You will find examples of doing this in [evennia.utils.tests.test_evmenu](evennia.utils.tests.test_evmenu).
Unit-testing the menu is more complex. You will find examples of doing this in [evennia.utils.tests.test_evmenu](github:main/evennia/utils/testss/test_evmenu.py).
## A small combat test
@ -1219,7 +1222,7 @@ Unit testing the code is not enough to see that combat works. We need to also ma
- An item (like a potion) we can `use`.
```{sidebar}
You can find an example batch-code script in [evennia/contrib/tutorials/evadventure/batchscripts/turnbased_combat_demo.ev](evennia.contrib.tutorials.evadventure.batchscripts)
You can find an example batch-code script in [evennia/contrib/tutorials/evadventure/batchscripts/turnbased_combat_demo.py](github:evennia/contrib/tutorials/evadventure/batchscripts/turnbased_combat_demo.py)
```
In [The Twitch combat lesson](./Beginner-Tutorial-Combat-Twitch.md) we used a [batch-command script](../../../Components/Batch-Command-Processor.md) to create the testing environment in game. This runs in-game Evennia commands in sequence. For demonstration purposes we'll instead use a [batch-code script](../../../Components/Batch-Code-Processor.md), which runs raw Python code in a repeatable way. A batch-code script is much more flexible than a batch-command script.

View file

@ -67,14 +67,14 @@ An example of an implemented Twitch combat system can be found in [evennia/contr
Here is the general design of the Twitch-based combat handler:
- The twitch-version of the CombatHandler will be stored on each combatant whenever combat starts. When combat is over, or they leave the room with combat, the handler will be deleted.
- The handler will start queue each action independently, starting a timer until they fire.
- The handler will queue each action independently, starting a timer until they fire.
- All input are handled via Commands.
## Twitch combat handler
> Create a new module `evadventure/combat_twitch.py`.
We will make use of the _Combat Actions_, _Combat dicts_ and the parent `EvAdventureCombatBaseHandler` [we created previously](./Beginner-Tutorial-Combat-Base.md).
We will make use of the _Combat Actions_, _Action dicts_ and the parent `EvAdventureCombatBaseHandler` [we created previously](./Beginner-Tutorial-Combat-Base.md).
```python
# in evadventure/combat_twitch.py
@ -941,7 +941,7 @@ This is what we need for a minimal test:
- An item (like a potion) we can `use`.
```{sidebar}
You can find an example batch-command script in [evennia/contrib/tutorials/evadventure/batchscripts/twitch_combat_demo.ev](evennia.contrib.tutorials.evadventure.batchscripts)
You can find an example batch-command script in [evennia/contrib/tutorials/evadventure/batchscripts/twitch_combat_demo.ev](github:evennia/contrib/tutorials/evadventure/batchscripts/turnbased_combat_demo.ev)
```
While you can create these manually in-game, it can be convenient to create a [batch-command script](../../../Components/Batch-Command-Processor.md) to set up your testing environment.

View file

@ -22,15 +22,21 @@ The tutorial game is under development and is not yet complete, nor tested. Use
In part three of the Evennia Beginner tutorial we will go through the actual creation of
our tutorial game _EvAdventure_, based on the [Knave](https://www.drivethrurpg.com/product/250888/Knave) RPG ruleset.
Even if this is not the game-style you are interested in, following along will give you a lot
of experience using Evennia and be really helpful for doing your own thing later!
This is a big part. You'll be seeing a lot of code and there are plenty of lessons to go through. Take your time!
If you followed the previous parts of this tutorial series you will have some notions about Python and where to find and make use of things in Evennia. We also have a good idea of the type of game we will create.
Fully coded examples of all code we make in this part can be found in the
[evennia/contrib/tutorials/evadventure](../../../api/evennia.contrib.tutorials.evadventure.md) package.
Even if this is not the game-style you are interested in, following along will give you a lot
of experience using Evennia and be really helpful for doing your own thing later! The EvAdventure game code is also built to easily be expanded upon.
Fully coded examples of all code we make in this part can be found in the
[evennia/contrib/tutorials/evadventure](../../../api/evennia.contrib.tutorials.evadventure.md) package. There are three common ways to learn from this:
1. Follow the tutorial lessons in sequence and use it to write your own code, referring to the ready-made code as extra help, context, or as a 'facit' to check yourself.
2. Read through the code in the package and refer to the tutorial lesson for each part for more information on what you see.
3. Some mix of the two.
Which approach you choose is individual - we all learn in different ways.
Either way, this is a big part. You'll be seeing a lot of code and there are plenty of lessons to go through. We are making a whole game from scratch after all. Take your time!
## Lessons

View file

@ -0,0 +1,17 @@
```{eval-rst}
evennia.contrib.tutorials.evadventure.batchscripts
==========================================================
.. automodule:: evennia.contrib.tutorials.evadventure.batchscripts
:members:
:undoc-members:
:show-inheritance:
.. toctree::
:maxdepth: 6
evennia.contrib.tutorials.evadventure.batchscripts.turnbased_combat_demo
```

View file

@ -0,0 +1,10 @@
```{eval-rst}
evennia.contrib.tutorials.evadventure.batchscripts.turnbased\_combat\_demo
=================================================================================
.. automodule:: evennia.contrib.tutorials.evadventure.batchscripts.turnbased_combat_demo
:members:
:undoc-members:
:show-inheritance:
```

View file

@ -36,6 +36,7 @@ evennia.contrib.tutorials.evadventure
.. toctree::
:maxdepth: 6
evennia.contrib.tutorials.evadventure.batchscripts
evennia.contrib.tutorials.evadventure.tests
```

View file

@ -179,7 +179,7 @@ def url_resolver(app, docname, source):
Convert urls by catching special markers.
Supported replacements (used e.g. as [txt](github:...)
github:master/<url> - add path to Evennia github master branch
github:main/<url> - add path to Evennia github master branch
github:develop/<url> - add path to Evennia github develop branch
github:issue - add link to the Evennia github issue-create page
src:foo.bar#Foo - add link to source doc in _modules
@ -189,7 +189,6 @@ def url_resolver(app, docname, source):
"""
def _url_remap(url):
# determine depth in tree of current document
docdepth = docname.count("/") + 1
relative_path = "../".join("" for _ in range(docdepth))
@ -294,7 +293,6 @@ def autodoc_post_process_docstring(app, what, name, obj, options, lines):
Post-process docstring in various ways. Must modify lines-list in-place.
"""
try:
# clean out ANSI colors
if ansi_clean:

View file

@ -1,19 +1,18 @@
# EvAdventure
Contrib by Griatch 2022
Contrib by Griatch 2023-
```{warning}
NOTE - this tutorial is WIP and NOT complete! It was put on hold to focus on
releasing Evennia 1.0. You will still learn things from it, but don't expect
perfection.
NOTE - this tutorial is WIP and NOT complete yet! You will still learn
things from it, but don't expect perfection.
```
A complete example MUD using Evennia. This is the final result of what is
implemented if you follow the Getting-Started tutorial. It's recommended
that you follow the tutorial step by step and write your own code. But if
you prefer you can also pick apart or use this as a starting point for your
own game.
implemented if you follow [Part 3 of the Getting-Started tutorial](Beginner-Tutorial-Part3-Overview).
It's recommended that you follow the tutorial step by step and write your own
code. But if you prefer you can also pick apart or use this as a starting point
for your own game.
## Features

View file

@ -0,0 +1 @@
# This file needs to be here for autodocs to pick up this folder/package

View file

@ -3,13 +3,15 @@ EvAdventure Base combat utilities.
This establishes the basic building blocks for combat:
- `CombatFailure` - exception for combat-specific errors.
- `CombatAction` (and subclasses) - classes encompassing all the working around an action.
They are initialized from 'action-dicts` - dictionaries with all the relevant data for the
particular invocation
- `CombatHandler` - base class for running a combat. Exactly how this is used depends on the
type of combat intended (twitch- or turn-based) so many details of this will be implemented
in child classes.
- `CombatFailure` - exception for combat-specific errors.
- `CombatAction` (and subclasses) - classes encompassing all the working around an action.
They are initialized from 'action-dicts` - dictionaries with all the relevant data for the
particular invocation
- `CombatHandler` - base class for running a combat. Exactly how this is used depends on the
type of combat intended (twitch- or turn-based) so many details of this will be implemented
in child classes.
----
"""
@ -23,7 +25,7 @@ from . import rules
class CombatFailure(RuntimeError):
"""
Some failure during actions.
Some failure during combat actions.
"""
@ -98,28 +100,21 @@ class CombatAction:
class CombatActionHold(CombatAction):
"""
Action that does nothing.
Note:
Refer to as 'hold'
action_dict = {
"key": "hold"
}
::
action_dict = {
"key": "hold"
}
"""
class CombatActionAttack(CombatAction):
"""
A regular attack, using a wielded weapon.
action-dict = {
"key": "attack",
"target": Character/Object
}
Note:
Refer to as 'attack'
::
action-dict = {
"key": "attack",
"target": Character/Object
}
"""
def execute(self):
@ -140,19 +135,16 @@ class CombatActionStunt(CombatAction):
target. Whenever performing a stunt that would affect another negatively (giving them
disadvantage against an ally, or granting an advantage against them, we need to make a check
first. We don't do a check if giving an advantage to an ally or ourselves.
action_dict = {
"key": "stunt",
"recipient": Character/NPC,
"target": Character/NPC,
"advantage": bool, # if False, it's a disadvantage
"stunt_type": Ability, # what ability (like STR, DEX etc) to use to perform this stunt.
"defense_type": Ability, # what ability to use to defend against (negative) effects of
this stunt.
}
Note:
refer to as 'stunt'.
::
action_dict = {
"key": "stunt",
"recipient": Character/NPC,
"target": Character/NPC,
"advantage": bool, # if False, it's a disadvantage
"stunt_type": Ability, # what ability (like STR, DEX etc) to use to perform this stunt.
"defense_type": Ability, # what ability to use to defend against (negative) effects of
this stunt.
}
"""
@ -209,16 +201,12 @@ class CombatActionUseItem(CombatAction):
Use an item in combat. This is meant for one-off or limited-use items (so things like
scrolls and potions, not swords and shields). If this is some sort of weapon or spell rune,
we refer to the item to determine what to use for attack/defense rolls.
action_dict = {
"key": "use",
"item": Object
"target": Character/NPC/Object/None
}
Note:
Refer to as 'use'
::
action_dict = {
"key": "use",
"item": Object
"target": Character/NPC/Object/None
}
"""
def execute(self):
@ -234,22 +222,17 @@ class CombatActionUseItem(CombatAction):
disadvantage=self.combathandler.has_disadvantage(user, target),
)
item.at_post_use(user, target)
# to back to idle after this
class CombatActionWield(CombatAction):
"""
Wield a new weapon (or spell) from your inventory. This will swap out the one you are currently
wielding, if any.
action_dict = {
"key": "wield",
"item": Object
}
Note:
Refer to as 'wield'.
::
action_dict = {
"key": "wield",
"item": Object
}
"""
def execute(self):

View file

@ -0,0 +1,4 @@
"""
Unit tests for EvAdventure components.
"""

View file

@ -159,18 +159,7 @@ class InMemoryAttribute(IAttribute):
class AttributeProperty:
"""
Attribute property descriptor. Allows for specifying Attributes as Django-like 'fields'
on the class level. Note that while one can set a lock on the Attribute,
there is no way to *check* said lock when accessing via the property - use
the full `AttributeHandler` if you need to do access checks. Note however that if you use the
full `AttributeHandler` to access this Attribute, the `at_get/at_set` methods on this class will
_not_ fire (because you are bypassing the `AttributeProperty` entirely in that case).
Example:
::
class Character(DefaultCharacter):
foo = AttributeProperty(default="Bar")
AttributeProperty.
"""
@ -178,6 +167,13 @@ class AttributeProperty:
def __init__(self, default=None, category=None, strattr=False, lockstring="", autocreate=True):
"""
Allows for specifying Attributes as Django-like 'fields' on the class level. Note that while
one can set a lock on the Attribute, there is no way to *check* said lock when accessing via
the property - use the full `AttributeHandler` if you need to do access checks. Note however
that if you use the full `AttributeHandler` to access this Attribute, the `at_get/at_set`
methods on this class will _not_ fire (because you are bypassing the `AttributeProperty`
entirely in that case).
Initialize an Attribute as a property descriptor.
Keyword Args:
@ -194,6 +190,11 @@ class AttributeProperty:
is explicitly assigned a value. This makes it more efficient while it retains
its default (there's no db access), but without an actual Attribute generated,
one cannot access it via .db, the AttributeHandler or see it with `examine`.
Example:
::
class Character(DefaultCharacter):
foo = AttributeProperty(default="Bar")
"""
self._default = default