Merge branch 'main' of https://github.com/evennia/evennia into editor_echo
This commit is contained in:
commit
4a1ff3deeb
18 changed files with 711 additions and 273 deletions
|
|
@ -780,13 +780,14 @@ class CmdSetHelp(CmdHelp):
|
|||
Edit the help database.
|
||||
|
||||
Usage:
|
||||
sethelp[/switches] <topic>[[;alias;alias][,category[,locks]] [= <text>]
|
||||
|
||||
sethelp[/switches] <topic>[[;alias;alias][,category[,locks]]
|
||||
[= <text or new category>]
|
||||
Switches:
|
||||
edit - open a line editor to edit the topic's help text.
|
||||
replace - overwrite existing help topic.
|
||||
append - add text to the end of existing topic with a newline between.
|
||||
extend - as append, but don't add a newline.
|
||||
category - change category of existing help topic.
|
||||
delete - remove help topic.
|
||||
|
||||
Examples:
|
||||
|
|
@ -794,6 +795,7 @@ class CmdSetHelp(CmdHelp):
|
|||
sethelp/append pickpocketing,Thievery = This steals ...
|
||||
sethelp/replace pickpocketing, ,attr(is_thief) = This steals ...
|
||||
sethelp/edit thievery
|
||||
sethelp/category thievery = classes
|
||||
|
||||
If not assigning a category, the `settings.DEFAULT_HELP_CATEGORY` category
|
||||
will be used. If no lockstring is specified, everyone will be able to read
|
||||
|
|
@ -840,7 +842,7 @@ class CmdSetHelp(CmdHelp):
|
|||
|
||||
key = "sethelp"
|
||||
aliases = []
|
||||
switch_options = ("edit", "replace", "append", "extend", "delete")
|
||||
switch_options = ("edit", "replace", "append", "extend", "category", "delete")
|
||||
locks = "cmd:perm(Helper)"
|
||||
help_category = "Building"
|
||||
arg_regex = None
|
||||
|
|
@ -857,7 +859,7 @@ class CmdSetHelp(CmdHelp):
|
|||
|
||||
if not self.args:
|
||||
self.msg(
|
||||
"Usage: sethelp[/switches] <topic>[;alias;alias][,category[,locks,..] = <text>"
|
||||
"Usage: sethelp[/switches] <topic>[[;alias;alias][,category[,locks]] [= <text or new category>]"
|
||||
)
|
||||
return
|
||||
|
||||
|
|
@ -953,7 +955,7 @@ class CmdSetHelp(CmdHelp):
|
|||
else:
|
||||
helpentry = create.create_help_entry(
|
||||
topicstr,
|
||||
self.rhs,
|
||||
self.rhs if self.rhs is not None else "",
|
||||
category=category,
|
||||
locks=lockstring,
|
||||
aliases=aliases,
|
||||
|
|
@ -986,6 +988,19 @@ class CmdSetHelp(CmdHelp):
|
|||
self.msg(f"Entry updated:\n{old_entry.entrytext}{aliastxt}")
|
||||
return
|
||||
|
||||
if "category" in switches:
|
||||
# set the category
|
||||
if not old_entry:
|
||||
self.msg(f"Could not find topic '{topicstr}'{aliastxt}.")
|
||||
return
|
||||
if not self.rhs:
|
||||
self.msg("You must supply a category.")
|
||||
return
|
||||
category = self.rhs.lower()
|
||||
old_entry.help_category = category
|
||||
self.msg(f"Category for entry '{topicstr}'{aliastxt} changed to '{category}'.")
|
||||
return
|
||||
|
||||
if "delete" in switches or "del" in switches:
|
||||
# delete the help entry
|
||||
if not old_entry:
|
||||
|
|
|
|||
|
|
@ -197,6 +197,12 @@ class TestHelp(BaseEvenniaCommandTest):
|
|||
cmdset=CharacterCmdSet(),
|
||||
)
|
||||
self.call(help_module.CmdHelp(), "testhelp", "Help for testhelp", cmdset=CharacterCmdSet())
|
||||
self.call(
|
||||
help_module.CmdSetHelp(),
|
||||
"/category testhelp = misc",
|
||||
"Category for entry 'testhelp' changed to 'misc'.",
|
||||
cmdset=CharacterCmdSet(),
|
||||
)
|
||||
|
||||
@parameterized.expand(
|
||||
[
|
||||
|
|
|
|||
|
|
@ -237,7 +237,7 @@ class CraftingRecipeBase:
|
|||
**kwargs: Any optional properties relevant to this send.
|
||||
|
||||
"""
|
||||
self.crafter.msg(message, {"type": "crafting"})
|
||||
self.crafter.msg(text=(message, {"type": "crafting"}))
|
||||
|
||||
def pre_craft(self, **kwargs):
|
||||
"""
|
||||
|
|
|
|||
|
|
@ -78,7 +78,7 @@ class TestCraftingRecipeBase(BaseEvenniaTestCase):
|
|||
"""Test messaging to crafter"""
|
||||
|
||||
self.recipe.msg("message")
|
||||
self.crafter.msg.assert_called_with("message", {"type": "crafting"})
|
||||
self.crafter.msg.assert_called_with(text=("message", {"type": "crafting"}))
|
||||
|
||||
def test_pre_craft(self):
|
||||
"""Test validating hook"""
|
||||
|
|
@ -206,7 +206,7 @@ class TestCraftingRecipe(BaseEvenniaTestCase):
|
|||
self.assertEqual(result[0].key, "Result1")
|
||||
self.assertEqual(result[0].tags.all(), ["result1", "resultprot"])
|
||||
self.crafter.msg.assert_called_with(
|
||||
recipe.success_message.format(outputs="Result1"), {"type": "crafting"}
|
||||
text=(recipe.success_message.format(outputs="Result1"), {"type": "crafting"})
|
||||
)
|
||||
|
||||
# make sure consumables are gone
|
||||
|
|
@ -235,7 +235,7 @@ class TestCraftingRecipe(BaseEvenniaTestCase):
|
|||
self.assertEqual(result[0].key, "Result1")
|
||||
self.assertEqual(result[0].tags.all(), ["result1", "resultprot"])
|
||||
self.crafter.msg.assert_called_with(
|
||||
recipe.success_message.format(outputs="Result1"), {"type": "crafting"}
|
||||
text=(recipe.success_message.format(outputs="Result1"), {"type": "crafting"})
|
||||
)
|
||||
|
||||
# make sure consumables are gone
|
||||
|
|
@ -251,8 +251,10 @@ class TestCraftingRecipe(BaseEvenniaTestCase):
|
|||
result = recipe.craft()
|
||||
self.assertFalse(result)
|
||||
self.crafter.msg.assert_called_with(
|
||||
recipe.error_tool_missing_message.format(outputs="Result1", missing="tool2"),
|
||||
{"type": "crafting"},
|
||||
text=(
|
||||
recipe.error_tool_missing_message.format(outputs="Result1", missing="tool2"),
|
||||
{"type": "crafting"},
|
||||
)
|
||||
)
|
||||
|
||||
# make sure consumables are still there
|
||||
|
|
@ -269,8 +271,10 @@ class TestCraftingRecipe(BaseEvenniaTestCase):
|
|||
result = recipe.craft()
|
||||
self.assertFalse(result)
|
||||
self.crafter.msg.assert_called_with(
|
||||
recipe.error_consumable_missing_message.format(outputs="Result1", missing="cons3"),
|
||||
{"type": "crafting"},
|
||||
text=(
|
||||
recipe.error_consumable_missing_message.format(outputs="Result1", missing="cons3"),
|
||||
{"type": "crafting"},
|
||||
)
|
||||
)
|
||||
|
||||
# make sure consumables are still there
|
||||
|
|
@ -293,8 +297,10 @@ class TestCraftingRecipe(BaseEvenniaTestCase):
|
|||
|
||||
self.assertFalse(result)
|
||||
self.crafter.msg.assert_called_with(
|
||||
recipe.error_consumable_missing_message.format(outputs="Result1", missing="cons3"),
|
||||
{"type": "crafting"},
|
||||
text=(
|
||||
recipe.error_consumable_missing_message.format(outputs="Result1", missing="cons3"),
|
||||
{"type": "crafting"},
|
||||
)
|
||||
)
|
||||
|
||||
# make sure consumables are deleted even though we failed
|
||||
|
|
@ -317,10 +323,12 @@ class TestCraftingRecipe(BaseEvenniaTestCase):
|
|||
result = recipe.craft()
|
||||
self.assertFalse(result)
|
||||
self.crafter.msg.assert_called_with(
|
||||
recipe.error_tool_excess_message.format(
|
||||
outputs="Result1", excess=wrong.get_display_name(looker=self.crafter)
|
||||
),
|
||||
{"type": "crafting"},
|
||||
text=(
|
||||
recipe.error_tool_excess_message.format(
|
||||
outputs="Result1", excess=wrong.get_display_name(looker=self.crafter)
|
||||
),
|
||||
{"type": "crafting"},
|
||||
)
|
||||
)
|
||||
# make sure consumables are still there
|
||||
self.assertIsNotNone(self.cons1.pk)
|
||||
|
|
@ -342,10 +350,12 @@ class TestCraftingRecipe(BaseEvenniaTestCase):
|
|||
result = recipe.craft()
|
||||
self.assertFalse(result)
|
||||
self.crafter.msg.assert_called_with(
|
||||
recipe.error_tool_excess_message.format(
|
||||
outputs="Result1", excess=tool3.get_display_name(looker=self.crafter)
|
||||
),
|
||||
{"type": "crafting"},
|
||||
text=(
|
||||
recipe.error_tool_excess_message.format(
|
||||
outputs="Result1", excess=tool3.get_display_name(looker=self.crafter)
|
||||
),
|
||||
{"type": "crafting"},
|
||||
)
|
||||
)
|
||||
|
||||
# make sure consumables are still there
|
||||
|
|
@ -369,10 +379,12 @@ class TestCraftingRecipe(BaseEvenniaTestCase):
|
|||
result = recipe.craft()
|
||||
self.assertFalse(result)
|
||||
self.crafter.msg.assert_called_with(
|
||||
recipe.error_consumable_excess_message.format(
|
||||
outputs="Result1", excess=cons4.get_display_name(looker=self.crafter)
|
||||
),
|
||||
{"type": "crafting"},
|
||||
text=(
|
||||
recipe.error_consumable_excess_message.format(
|
||||
outputs="Result1", excess=cons4.get_display_name(looker=self.crafter)
|
||||
),
|
||||
{"type": "crafting"},
|
||||
)
|
||||
)
|
||||
|
||||
# make sure consumables are still there
|
||||
|
|
@ -396,7 +408,7 @@ class TestCraftingRecipe(BaseEvenniaTestCase):
|
|||
result = recipe.craft()
|
||||
self.assertTrue(result)
|
||||
self.crafter.msg.assert_called_with(
|
||||
recipe.success_message.format(outputs="Result1"), {"type": "crafting"}
|
||||
text=(recipe.success_message.format(outputs="Result1"), {"type": "crafting"})
|
||||
)
|
||||
|
||||
# make sure consumables are gone
|
||||
|
|
@ -419,7 +431,7 @@ class TestCraftingRecipe(BaseEvenniaTestCase):
|
|||
result = recipe.craft()
|
||||
self.assertTrue(result)
|
||||
self.crafter.msg.assert_called_with(
|
||||
recipe.success_message.format(outputs="Result1"), {"type": "crafting"}
|
||||
text=(recipe.success_message.format(outputs="Result1"), {"type": "crafting"})
|
||||
)
|
||||
|
||||
# make sure consumables are gone
|
||||
|
|
@ -439,10 +451,12 @@ class TestCraftingRecipe(BaseEvenniaTestCase):
|
|||
result = recipe.craft()
|
||||
self.assertFalse(result)
|
||||
self.crafter.msg.assert_called_with(
|
||||
recipe.error_tool_order_message.format(
|
||||
outputs="Result1", missing=self.tool2.get_display_name(looker=self.crafter)
|
||||
),
|
||||
{"type": "crafting"},
|
||||
text=(
|
||||
recipe.error_tool_order_message.format(
|
||||
outputs="Result1", missing=self.tool2.get_display_name(looker=self.crafter)
|
||||
),
|
||||
{"type": "crafting"},
|
||||
)
|
||||
)
|
||||
|
||||
# make sure consumables are still there
|
||||
|
|
@ -462,10 +476,12 @@ class TestCraftingRecipe(BaseEvenniaTestCase):
|
|||
result = recipe.craft()
|
||||
self.assertFalse(result)
|
||||
self.crafter.msg.assert_called_with(
|
||||
recipe.error_consumable_order_message.format(
|
||||
outputs="Result1", missing=self.cons3.get_display_name(looker=self.crafter)
|
||||
),
|
||||
{"type": "crafting"},
|
||||
text=(
|
||||
recipe.error_consumable_order_message.format(
|
||||
outputs="Result1", missing=self.cons3.get_display_name(looker=self.crafter)
|
||||
),
|
||||
{"type": "crafting"},
|
||||
)
|
||||
)
|
||||
|
||||
# make sure consumables are still there
|
||||
|
|
|
|||
|
|
@ -47,17 +47,8 @@ from collections import deque
|
|||
|
||||
from django.conf import settings
|
||||
from django.db.models import Q
|
||||
|
||||
from evennia import (
|
||||
CmdSet,
|
||||
DefaultRoom,
|
||||
EvEditor,
|
||||
FuncParser,
|
||||
InterruptCommand,
|
||||
default_cmds,
|
||||
gametime,
|
||||
utils,
|
||||
)
|
||||
from evennia import (CmdSet, DefaultRoom, EvEditor, FuncParser,
|
||||
InterruptCommand, default_cmds, gametime, utils)
|
||||
from evennia.typeclasses.attributes import AttributeProperty
|
||||
from evennia.utils.utils import list_to_string, repeat
|
||||
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ import time
|
|||
|
||||
from anything import Anything
|
||||
from evennia import DefaultObject, create_object, default_cmds
|
||||
from evennia.commands.default import building
|
||||
from evennia.commands.default.tests import BaseEvenniaCommandTest
|
||||
from evennia.utils.test_resources import BaseEvenniaTest
|
||||
|
||||
|
|
@ -413,10 +414,9 @@ class TestRPSystemCommands(BaseEvenniaCommandTest):
|
|||
|
||||
expected_first_call = [
|
||||
"More than one match for 'Mushroom' (please narrow target):",
|
||||
f" Mushroom-1 []",
|
||||
f" Mushroom-2 []",
|
||||
f" Mushroom-1",
|
||||
f" Mushroom-2",
|
||||
]
|
||||
|
||||
self.call(default_cmds.CmdLook(), "Mushroom", "\n".join(expected_first_call)) # PASSES
|
||||
|
||||
expected_second_call = f"Mushroom(#{mushroom1.id})\nThe first mushroom is brown."
|
||||
|
|
@ -424,3 +424,13 @@ class TestRPSystemCommands(BaseEvenniaCommandTest):
|
|||
|
||||
expected_third_call = f"Mushroom(#{mushroom2.id})\nThe second mushroom is red."
|
||||
self.call(default_cmds.CmdLook(), "Mushroom-2", expected_third_call) # FAILS
|
||||
|
||||
expected_fourth_call = "Alias(es) for 'Mushroom' set to 'fungus'."
|
||||
self.call(building.CmdSetObjAlias(), "Mushroom-1 = fungus", expected_fourth_call) #PASSES
|
||||
|
||||
expected_fifth_call = [
|
||||
"More than one match for 'Mushroom' (please narrow target):",
|
||||
f" Mushroom-1 [fungus]",
|
||||
f" Mushroom-2",
|
||||
]
|
||||
self.call(default_cmds.CmdLook(), "Mushroom", "\n".join(expected_fifth_call)) # PASSES
|
||||
|
|
|
|||
|
|
@ -84,15 +84,3 @@ class ObjType(Enum):
|
|||
MAGIC = "magic"
|
||||
QUEST = "quest"
|
||||
TREASURE = "treasure"
|
||||
|
||||
|
||||
class QuestStatus(Enum):
|
||||
"""
|
||||
Quest status
|
||||
|
||||
"""
|
||||
|
||||
STARTED = "started"
|
||||
COMPLETED = "completed"
|
||||
ABANDONED = "abandoned"
|
||||
FAILED = "failed"
|
||||
|
|
|
|||
|
|
@ -2,19 +2,17 @@
|
|||
A simple quest system for EvAdventure.
|
||||
|
||||
A quest is represented by a quest-handler sitting as
|
||||
.quest on a Character. Individual Quests are objects
|
||||
that track the state and can have multiple steps, each
|
||||
of which are checked off during the quest's progress.
|
||||
|
||||
The player can use the quest handler to track the
|
||||
progress of their quests.
|
||||
`.quests` on a Character. Individual Quests are child classes of `EvAdventureQuest` with
|
||||
methods for each step of the quest. The quest handler can add, remove, and track the progress
|
||||
by calling the `progress` method on the quest. Persistent changes are stored on the quester
|
||||
using the `add_data` and `get_data` methods with an Attribute as storage backend.
|
||||
|
||||
A quest ending can mean a reward or the start of
|
||||
another quest.
|
||||
|
||||
"""
|
||||
|
||||
from .enums import QuestStatus
|
||||
from evennia import Command
|
||||
|
||||
|
||||
class EvAdventureQuest:
|
||||
|
|
@ -47,6 +45,9 @@ class EvAdventureQuest:
|
|||
self.current_step = "end"
|
||||
self.progress()
|
||||
|
||||
def step_B(self, *args, **kwargs):
|
||||
|
||||
|
||||
def step_end(self, *args, **kwargs):
|
||||
if len(self.quester.contents) > 4:
|
||||
self.quester.msg("Quest complete!")
|
||||
|
|
@ -54,62 +55,23 @@ class EvAdventureQuest:
|
|||
```
|
||||
"""
|
||||
|
||||
key = "basequest"
|
||||
key = "base quest"
|
||||
desc = "This is the base quest class"
|
||||
start_step = "start"
|
||||
|
||||
completed_text = "This quest is completed!"
|
||||
abandoned_text = "This quest is abandoned."
|
||||
|
||||
# help entries for quests (could also be methods)
|
||||
help_start = "You need to start first"
|
||||
help_end = "You need to end the quest"
|
||||
|
||||
def __init__(self, quester, data=None):
|
||||
if " " in self.key:
|
||||
raise TypeError("The Quest name must not have spaces in it.")
|
||||
|
||||
def __init__(self, quester):
|
||||
self.quester = quester
|
||||
self.data = data or dict()
|
||||
self.data = self.questhandler.load_quest_data(self.key)
|
||||
self._current_step = self.get_data("current_step")
|
||||
|
||||
if not self.current_step:
|
||||
self.current_step = self.start_step
|
||||
|
||||
@property
|
||||
def questhandler(self):
|
||||
return self.quester.quests
|
||||
|
||||
@property
|
||||
def current_step(self):
|
||||
return self._current_step
|
||||
|
||||
@current_step.setter
|
||||
def current_step(self, step_name):
|
||||
self._current_step = step_name
|
||||
self.add_data("current_step", step_name)
|
||||
self.questhandler.do_save = True
|
||||
|
||||
@property
|
||||
def status(self):
|
||||
return self.get_data("status", QuestStatus.STARTED)
|
||||
|
||||
@status.setter
|
||||
def status(self, value):
|
||||
self.add_data("status", value)
|
||||
|
||||
@property
|
||||
def is_completed(self):
|
||||
return self.status == QuestStatus.COMPLETED
|
||||
|
||||
@property
|
||||
def is_abandoned(self):
|
||||
return self.status == QuestStatus.ABANDONED
|
||||
|
||||
@property
|
||||
def is_failed(self):
|
||||
return self.status == QuestStatus.FAILED
|
||||
|
||||
def add_data(self, key, value):
|
||||
"""
|
||||
Add data to the quest. This saves it permanently.
|
||||
|
|
@ -120,18 +82,7 @@ class EvAdventureQuest:
|
|||
|
||||
"""
|
||||
self.data[key] = value
|
||||
self.questhandler.save_quest_data(self.key, self.data)
|
||||
|
||||
def remove_data(self, key):
|
||||
"""
|
||||
Remove data from the quest permanently.
|
||||
|
||||
Args:
|
||||
key (str): The key to remove.
|
||||
|
||||
"""
|
||||
self.data.pop(key, None)
|
||||
self.questhandler.save_quest_data(self.key, self.data)
|
||||
self.questhandler.save_quest_data(self.key)
|
||||
|
||||
def get_data(self, key, default=None):
|
||||
"""
|
||||
|
|
@ -147,23 +98,70 @@ class EvAdventureQuest:
|
|||
"""
|
||||
return self.data.get(key, default)
|
||||
|
||||
def abandon(self):
|
||||
def remove_data(self, key):
|
||||
"""
|
||||
Call when quest is abandoned.
|
||||
Remove data from the quest permanently.
|
||||
|
||||
Args:
|
||||
key (str): The key to remove.
|
||||
|
||||
"""
|
||||
self.add_data("status", QuestStatus.ABANDONED)
|
||||
self.questhandler.clean_quest_data(self.key)
|
||||
self.cleanup()
|
||||
self.data.pop(key, None)
|
||||
self.questhandler.save_quest_data(self.key)
|
||||
|
||||
@property
|
||||
def questhandler(self):
|
||||
return self.quester.quests
|
||||
|
||||
@property
|
||||
def current_step(self):
|
||||
return self._current_step
|
||||
|
||||
@current_step.setter
|
||||
def current_step(self, step_name):
|
||||
self._current_step = step_name
|
||||
self.add_data("current_step", step_name)
|
||||
|
||||
@property
|
||||
def status(self):
|
||||
return self.get_data("status", "started")
|
||||
|
||||
@status.setter
|
||||
def status(self, value):
|
||||
self.add_data("status", value)
|
||||
|
||||
@property
|
||||
def is_completed(self):
|
||||
return self.status == "completed"
|
||||
|
||||
@property
|
||||
def is_abandoned(self):
|
||||
return self.status == "abandoned"
|
||||
|
||||
@property
|
||||
def is_failed(self):
|
||||
return self.status == "failed"
|
||||
|
||||
def complete(self):
|
||||
"""
|
||||
Call this to end the quest.
|
||||
Complete the quest.
|
||||
|
||||
"""
|
||||
self.add_data("status", QuestStatus.COMPLETED)
|
||||
self.questhandler.clean_quest_data(self.key)
|
||||
self.cleanup()
|
||||
self.status = "completed"
|
||||
|
||||
def abandon(self):
|
||||
"""
|
||||
Abandon the quest.
|
||||
|
||||
"""
|
||||
self.status = "abandoned"
|
||||
|
||||
def fail(self):
|
||||
"""
|
||||
Fail the quest.
|
||||
|
||||
"""
|
||||
self.status = "failed"
|
||||
|
||||
def progress(self, *args, **kwargs):
|
||||
"""
|
||||
|
|
@ -174,34 +172,38 @@ class EvAdventureQuest:
|
|||
Args:
|
||||
*args, **kwargs: Will be passed into the step method.
|
||||
|
||||
"""
|
||||
return getattr(self, f"step_{self.current_step}")(*args, **kwargs)
|
||||
Notes:
|
||||
`self.quester` is available as the character following the quest.
|
||||
|
||||
def help(self):
|
||||
"""
|
||||
getattr(self, f"step_{self.current_step}")(*args, **kwargs)
|
||||
|
||||
def help(self, *args, **kwargs):
|
||||
"""
|
||||
This is used to get help (or a reminder) of what needs to be done to complete the current
|
||||
quest-step.
|
||||
quest-step. It will look for a `help_<stepname>` method or string attribute on the quest.
|
||||
|
||||
Args:
|
||||
*args, **kwargs: Will be passed into any help_* method.
|
||||
|
||||
Returns:
|
||||
str: The help text for the current step.
|
||||
|
||||
"""
|
||||
if self.is_completed:
|
||||
return self.completed_text
|
||||
if self.is_abandoned:
|
||||
return self.abandoned_text
|
||||
if self.status in ("abandoned", "completed", "failed"):
|
||||
help_resource = getattr(self, f"help_{self.status}",
|
||||
f"You have {self.status} this quest.")
|
||||
else:
|
||||
help_resource = getattr(self, f"help_{self.current_step}", "No help available.")
|
||||
|
||||
help_resource = (
|
||||
getattr(self, f"help_{self.current_step}", None)
|
||||
or "You need to {self.current_step} ..."
|
||||
)
|
||||
if callable(help_resource):
|
||||
# the help_<current_step> can be a method to call
|
||||
return help_resource()
|
||||
# the help_* methods can be used to dynamically generate help
|
||||
return help_resource(*args, **kwargs)
|
||||
else:
|
||||
# normally it's just a string
|
||||
return str(help_resource)
|
||||
|
||||
|
||||
# step methods and hooks
|
||||
|
||||
def step_start(self, *args, **kwargs):
|
||||
|
|
@ -243,7 +245,6 @@ class EvAdventureQuestHandler:
|
|||
|
||||
def __init__(self, obj):
|
||||
self.obj = obj
|
||||
self.do_save = False
|
||||
self.quests = {}
|
||||
self.quest_classes = {}
|
||||
self._load()
|
||||
|
|
@ -256,7 +257,7 @@ class EvAdventureQuestHandler:
|
|||
)
|
||||
# instantiate all quests
|
||||
for quest_key, quest_class in self.quest_classes.items():
|
||||
self.quests[quest_key] = quest_class(self.obj, self.load_quest_data(quest_key))
|
||||
self.quests[quest_key] = quest_class(self.obj)
|
||||
|
||||
def _save(self):
|
||||
self.obj.attributes.add(
|
||||
|
|
@ -264,57 +265,6 @@ class EvAdventureQuestHandler:
|
|||
self.quest_classes,
|
||||
category=self.quest_storage_attribute_category,
|
||||
)
|
||||
self._load() # important
|
||||
self.do_save = False
|
||||
|
||||
def save_quest_data(self, quest_key, data):
|
||||
"""
|
||||
Save data for a quest. We store this on the quester as well as updating the quest itself.
|
||||
|
||||
Args:
|
||||
data (dict): The data to store. This is commonly flags or other data needed to track the
|
||||
quest.
|
||||
|
||||
"""
|
||||
quest = self.get(quest_key)
|
||||
if quest:
|
||||
quest.data = data
|
||||
self.obj.attributes.add(
|
||||
self.quest_data_attribute_template.format(quest_key=quest_key),
|
||||
data,
|
||||
category=self.quest_data_attribute_category,
|
||||
)
|
||||
|
||||
def load_quest_data(self, quest_key):
|
||||
"""
|
||||
Load data for a quest.
|
||||
|
||||
Args:
|
||||
quest_key (str): The quest to load data for.
|
||||
|
||||
Returns:
|
||||
dict: The data stored for the quest.
|
||||
|
||||
"""
|
||||
return self.obj.attributes.get(
|
||||
self.quest_data_attribute_template.format(quest_key=quest_key),
|
||||
category=self.quest_data_attribute_category,
|
||||
default={},
|
||||
)
|
||||
|
||||
def clean_quest_data(self, quest_key):
|
||||
"""
|
||||
Remove data for a quest.
|
||||
|
||||
Args:
|
||||
quest_key (str): The quest to remove data for.
|
||||
|
||||
"""
|
||||
self.obj.attributes.remove(
|
||||
self.quest_data_attribute_template.format(quest_key=quest_key),
|
||||
category=self.quest_data_attribute_category,
|
||||
)
|
||||
|
||||
|
||||
def has(self, quest_key):
|
||||
"""
|
||||
|
|
@ -344,6 +294,16 @@ class EvAdventureQuestHandler:
|
|||
"""
|
||||
return self.quests.get(quest_key)
|
||||
|
||||
def all(self):
|
||||
"""
|
||||
Get all quests stored on character.
|
||||
|
||||
Returns:
|
||||
list: All quests stored on character.
|
||||
|
||||
"""
|
||||
return list(self.quests.values())
|
||||
|
||||
def add(self, quest_class):
|
||||
"""
|
||||
Add a new quest
|
||||
|
|
@ -353,6 +313,7 @@ class EvAdventureQuestHandler:
|
|||
|
||||
"""
|
||||
self.quest_classes[quest_class.key] = quest_class
|
||||
self.quests[quest_class.key] = quest_class(self.obj)
|
||||
self._save()
|
||||
|
||||
def remove(self, quest_key):
|
||||
|
|
@ -368,50 +329,74 @@ class EvAdventureQuestHandler:
|
|||
# make sure to cleanup
|
||||
quest.abandon()
|
||||
self.quest_classes.pop(quest_key, None)
|
||||
self.quests.pop(quest_key, None)
|
||||
self._save()
|
||||
|
||||
def get_help(self, quest_key=None):
|
||||
def save_quest_data(self, quest_key):
|
||||
"""
|
||||
Get help text for a quest or for all quests. The help text is
|
||||
a combination of the description of the quest and the help-text
|
||||
of the current step.
|
||||
Save data for a quest. We store this on the quester as well as updating the quest itself.
|
||||
|
||||
Args:
|
||||
quest_key (str, optional): The quest-key. If not given, get help for all
|
||||
quests in handler.
|
||||
quest_key (str): The quest to save data for. The data is assumed to be stored on the
|
||||
quest as `.data` (a dict).
|
||||
|
||||
"""
|
||||
quest = self.get(quest_key)
|
||||
if quest:
|
||||
self.obj.attributes.add(
|
||||
self.quest_data_attribute_template.format(quest_key=quest_key),
|
||||
quest.data,
|
||||
category=self.quest_data_attribute_category,
|
||||
)
|
||||
|
||||
def load_quest_data(self, quest_key):
|
||||
"""
|
||||
Load data for a quest.
|
||||
|
||||
Args:
|
||||
quest_key (str): The quest to load data for.
|
||||
|
||||
Returns:
|
||||
list: Help texts, one for each quest, or only one if `quest_key` is given.
|
||||
dict: The data stored for the quest.
|
||||
|
||||
"""
|
||||
help_texts = []
|
||||
if quest_key in self.quests:
|
||||
quests = [self.quests[quest_key]]
|
||||
else:
|
||||
quests = self.quests.values()
|
||||
return self.obj.attributes.get(
|
||||
self.quest_data_attribute_template.format(quest_key=quest_key),
|
||||
category=self.quest_data_attribute_category,
|
||||
default={},
|
||||
)
|
||||
|
||||
|
||||
class CmdQuests(Command):
|
||||
"""
|
||||
List all quests and their statuses as well as get info about the status of
|
||||
a specific quest.
|
||||
|
||||
Usage:
|
||||
quests
|
||||
quest <questname>
|
||||
|
||||
"""
|
||||
key = "quests"
|
||||
aliases = ["quest"]
|
||||
|
||||
def parse(self):
|
||||
self.quest_name = self.args.strip()
|
||||
|
||||
def func(self):
|
||||
if self.quest_name:
|
||||
quest = self.caller.quests.get(self.quest_name)
|
||||
if not quest:
|
||||
self.msg(f"Quest {self.quest_name} not found.")
|
||||
return
|
||||
self.msg(f"Quest {quest.key}: {quest.status}\n{quest.help()}")
|
||||
return
|
||||
|
||||
quests = self.caller.quests.all()
|
||||
if not quests:
|
||||
self.msg("No quests.")
|
||||
return
|
||||
|
||||
for quest in quests:
|
||||
help_texts.append(f"|c{quest.key}|n\n {quest.desc}\n\n - {quest.help()}")
|
||||
return help_texts
|
||||
self.msg(f"Quest {quest.key}: {quest.status}")
|
||||
|
||||
def progress(self, quest_key=None, *args, **kwargs):
|
||||
"""
|
||||
Check progress of a given quest or all quests.
|
||||
|
||||
Args:
|
||||
quest_key (str, optional): If given, check the progress of this quest (if we have it),
|
||||
otherwise check progress on all quests.
|
||||
*args, **kwargs: Will be passed into each quest's `progress` call.
|
||||
|
||||
"""
|
||||
if quest_key in self.quests:
|
||||
quests = [self.quests[quest_key]]
|
||||
else:
|
||||
quests = self.quests.values()
|
||||
|
||||
for quest in quests:
|
||||
quest.progress(*args, **kwargs)
|
||||
|
||||
if self.do_save:
|
||||
# do_save is set by the quest
|
||||
self._save()
|
||||
|
|
|
|||
|
|
@ -99,52 +99,50 @@ class EvAdventureQuestTest(EvAdventureMixin, BaseEvenniaTest):
|
|||
|
||||
def test_help(self):
|
||||
"""Get help"""
|
||||
# get help for all quests
|
||||
help_txt = self.character.quests.get_help()
|
||||
self.assertEqual(help_txt, ["|ctestquest|n\n A test quest!\n\n - You need to do A first."])
|
||||
|
||||
# get help for one specific quest
|
||||
help_txt = self.character.quests.get_help(_TestQuest.key)
|
||||
self.assertEqual(help_txt, ["|ctestquest|n\n A test quest!\n\n - You need to do A first."])
|
||||
quest = self._get_quest()
|
||||
# get help for a specific quest
|
||||
help_txt = quest.help()
|
||||
self.assertEqual(help_txt, "You need to do A first.")
|
||||
|
||||
# help for finished quest
|
||||
self._get_quest().complete()
|
||||
help_txt = self.character.quests.get_help()
|
||||
self.assertEqual(help_txt, ["|ctestquest|n\n A test quest!\n\n - This quest is completed!"])
|
||||
quest.complete()
|
||||
help_txt = quest.help()
|
||||
self.assertEqual(help_txt, "You have completed this quest.")
|
||||
|
||||
def test_progress__fail(self):
|
||||
"""
|
||||
Check progress without having any.
|
||||
"""
|
||||
# progress all quests
|
||||
self.character.quests.progress()
|
||||
# progress one quest
|
||||
self.character.quests.progress(_TestQuest.key)
|
||||
quest = self._get_quest()
|
||||
# progress quest
|
||||
quest.progress()
|
||||
|
||||
# still on step A
|
||||
self.assertEqual(self._get_quest().current_step, "A")
|
||||
self.assertEqual(quest.current_step, "A")
|
||||
|
||||
def test_progress(self):
|
||||
"""
|
||||
Fulfill the quest steps in sequess
|
||||
Fulfill the quest steps in sequence.
|
||||
|
||||
"""
|
||||
quest = self._get_quest()
|
||||
|
||||
# A requires a certain object in inventory
|
||||
self._fulfillA()
|
||||
self.character.quests.progress()
|
||||
self.assertEqual(self._get_quest().current_step, "B")
|
||||
quest.progress()
|
||||
self.assertEqual(quest.current_step, "B")
|
||||
|
||||
# B requires progress be called with specific kwarg
|
||||
# should not step (no kwarg)
|
||||
self.character.quests.progress()
|
||||
self.assertEqual(self._get_quest().current_step, "B")
|
||||
quest.progress()
|
||||
self.assertEqual(quest.current_step, "B")
|
||||
|
||||
# should step (kwarg sent)
|
||||
self.character.quests.progress(complete_quest_B=True)
|
||||
self.assertEqual(self._get_quest().current_step, "C")
|
||||
quest.progress(complete_quest_B=True)
|
||||
self.assertEqual(quest.current_step, "C")
|
||||
|
||||
# C requires a counter Attribute on char be high enough
|
||||
self._fulfillC()
|
||||
self.character.quests.progress()
|
||||
self.assertEqual(self._get_quest().current_step, "C") # still on last step
|
||||
self.assertEqual(self._get_quest().is_completed, True)
|
||||
quest.progress()
|
||||
self.assertEqual(quest.current_step, "C") # still on last step
|
||||
self.assertEqual(quest.is_completed, True)
|
||||
|
|
|
|||
|
|
@ -227,7 +227,7 @@ class FileHelpStorageHandler:
|
|||
|
||||
for dct in loaded_help_dicts:
|
||||
key = dct.get("key").lower().strip()
|
||||
category = dct.get("category", _DEFAULT_HELP_CATEGORY).strip()
|
||||
category = dct.get("category", _DEFAULT_HELP_CATEGORY).lower().strip()
|
||||
aliases = list(dct.get("aliases", []))
|
||||
entrytext = dct.get("text", "")
|
||||
locks = dct.get("locks", "")
|
||||
|
|
|
|||
|
|
@ -138,5 +138,5 @@ class TestFileHelp(TestCase):
|
|||
for inum, helpentry in enumerate(result):
|
||||
self.assertEqual(HELP_ENTRY_DICTS[inum]["key"], helpentry.key)
|
||||
self.assertEqual(HELP_ENTRY_DICTS[inum].get("aliases", []), helpentry.aliases)
|
||||
self.assertEqual(HELP_ENTRY_DICTS[inum]["category"], helpentry.help_category)
|
||||
self.assertEqual(HELP_ENTRY_DICTS[inum]["category"].lower(), helpentry.help_category)
|
||||
self.assertEqual(HELP_ENTRY_DICTS[inum]["text"], helpentry.entrytext)
|
||||
|
|
|
|||
|
|
@ -2070,7 +2070,7 @@ def format_grid(elements, width=78, sep=" ", verbatim_elements=None, line_prefi
|
|||
else:
|
||||
row += " " * max(0, width - lrow)
|
||||
rows.append(row)
|
||||
row = ""
|
||||
row = element
|
||||
ic = 0
|
||||
else:
|
||||
# add a new slot
|
||||
|
|
@ -2403,9 +2403,9 @@ def at_search_result(matches, caller, query="", quiet=False, **kwargs):
|
|||
aliases = result.aliases.all(return_objs=True)
|
||||
# remove pluralization aliases
|
||||
aliases = [
|
||||
alias
|
||||
alias.db_key
|
||||
for alias in aliases
|
||||
if hasattr(alias, "category") and alias.category not in ("plural_key",)
|
||||
if alias.db_category != "plural_key"
|
||||
]
|
||||
else:
|
||||
# result is likely a Command, where `.aliases` is a list of strings.
|
||||
|
|
@ -2416,7 +2416,7 @@ def at_search_result(matches, caller, query="", quiet=False, **kwargs):
|
|||
name=result.get_display_name(caller)
|
||||
if hasattr(result, "get_display_name")
|
||||
else query,
|
||||
aliases=" [{alias}]".format(alias=";".join(aliases) if aliases else ""),
|
||||
aliases=" [{alias}]".format(alias=";".join(aliases)) if aliases else "",
|
||||
info=result.get_extra_info(caller),
|
||||
)
|
||||
matches = None
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue