Add set_dt/stage to on-demandhandler. Fix issue with loading

This commit is contained in:
Griatch 2024-03-06 00:13:09 +01:00
parent cc8584f839
commit 8dde1fbe38
3 changed files with 154 additions and 3 deletions

View file

@ -1,5 +1,12 @@
# Changelog
## Evennia Main branch
- Feature: Add `ON_DEMAND_HANDLER.set_dt(key, category, dt)` and
`.set_stage(key, category, stage)` to allow manual tweaking of task timings,
for example for a spell speeding a plant's growth. (Griatch)
- Fix: Resolve a bug when loading on-demand-handler data from database.
## Evennia 3.2.0
Feb 25, 2024

View file

@ -199,11 +199,17 @@ class OnDemandTask:
self.iterations = 0 # only used with looping staging functions
self.stages = None
self.stages_by_name = None
if isinstance(stages, dict):
# sort the stages by ending time, inserting each state as {dt: (statename, callable)}
_stages = {}
for dt, tup in stages.items():
# validate the input
if not isinstance(dt, (int, float)):
raise ValueError(
"Each stage must given as a time-delta in seconds (int or float)."
)
if is_iter(tup):
if len(tup) != 2:
raise ValueError(
@ -218,6 +224,7 @@ class OnDemandTask:
_stages[dt] = tup
self.stages = {dt: tup for dt, tup in sorted(_stages.items(), reverse=True)}
self.stages_by_name = {tup[0]: dt for dt, tup in self.stages.items()}
self.check(autostart=autostart)
@ -306,6 +313,23 @@ class OnDemandTask:
return self.check()[0]
def set_dt(self, dt):
"""
Set the time-delta since the task started manually. This allows you to 'cheat' the system
and set the time manually. This is useful for testing or when a system manipulates the state
somehow (like using a potion that speeds up the growth of a plant).
Args:
dt (int): The time-delta to set. This is an absolute value in seconds, same as returned
by `get_dt`.
Notes:
Setting this will not on its own trigger any stage functions - this will only happen
as normal, next time the state is checked and the stage is found to have changed.
"""
self.start_time = OnDemandTask.runtime() - dt
def get_stage(self):
"""
Get the current stage of the task. If no stage was given, this will return `None` but
@ -317,6 +341,30 @@ class OnDemandTask:
"""
return self.check()[1]
def set_stage(self, stage=None):
"""
Set the stage of the task manually. This allows you to 'cheat' the system and set the stage
manually. This is useful for testing or when a system manipulates the state somehow (like
using a potion that speeds up the growth of a plant). The given stage must be previously
created for the given task. If task has no stages, this will do nothing.
Args:
stage (str, optional): The stage to set. If `None`, the task will be reset to its
initial (first) state.
Notes:
Setting this will not on its own trigger any stage functions - this will only happen
as normal, next time the state is checked and the stage is found to have changed.
"""
if not self.stages:
return
if stage is None:
self.start_time = OnDemandTask.runtime() - min(self.stages.keys())
elif stage in self.stages_by_name:
self.start_time = OnDemandTask.runtime() - self.stages_by_name[stage]
class OnDemandHandler:
"""
@ -338,7 +386,7 @@ class OnDemandHandler:
This should be automatically called when Evennia starts.
"""
self.tasks = ServerConfig.objects.conf("on_demand_timers", default=dict)
self.tasks = dict(ServerConfig.objects.conf("on_demand_timers", default=dict))
def save(self):
"""
@ -520,6 +568,30 @@ class OnDemandHandler:
task = self.get(key, category)
return task.get_dt() if task else None
def set_dt(self, key, category, dt):
"""
Set the time-delta since the task started manually. This allows you to 'cheat' the system
and set the time manually. This is useful for testing or when a system manipulates the state
somehow (like using a potion that speeds up the growth of a plant).
Args:
key (str, callable, OnDemandTask or Object): The unique identifier for the task. If a
callable, will be called without arguments. If an Object, will be converted to a string.
If an `OnDemandTask`, then all other arguments are ignored and the task will be used
to identify the task to set the time-delta for.
category (str, optional): The category of the task.
dt (int): The time-delta to set. This is an absolute value in seconds, same as returned
by `get_dt`.
Notes:
Setting this will not on its own trigger any stage functions - this will only happen
as normal, next time the state is checked and the stage is found to have changed.
"""
task = self.get(key, category)
if task:
task.set_dt(dt)
def get_stage(self, key, category=None):
"""
Get the current stage of an on-demand task.
@ -537,6 +609,31 @@ class OnDemandHandler:
task = self.get(key, category)
return task.get_stage() if task else None
def set_stage(self, key, category=None, stage=None):
"""
Set the stage of an on-demand task manually. This allows you to 'cheat' the system and set
the stage manually. This is useful for testing or when a system manipulates the state
somehow (like using a potion that speeds up the growth of a plant). The given stage must
be previously created for the given task. If task has no stages, this will do nothing.
Args:
key (str, callable, OnDemandTask or Object): The unique identifier for the task. If a
callable, will be called without arguments. If an Object, will be converted to a
string. If an `OnDemandTask`, then all other arguments are ignored and the task
will be used to identify the task to set the stage for.
category (str, optional): The category of the task.
stage (str, optional): The stage to set. If `None`, the task will be reset to its
initial (first) state.
Notes:
Setting this will not on its own trigger any stage functions - this will only happen
as normal, next time the state is checked and the stage is found to have changed.
"""
task = self.get(key, category)
if task:
task.set_stage(stage)
# Create singleton
ON_DEMAND_HANDLER = OnDemandHandler()

View file

@ -6,8 +6,6 @@ Unit tests for the scripts package
from collections import defaultdict
from unittest import TestCase, mock
from parameterized import parameterized
from evennia import DefaultScript
from evennia.objects.objects import DefaultObject
from evennia.scripts.manager import ScriptDBManager
@ -19,6 +17,7 @@ from evennia.scripts.tickerhandler import TickerHandler
from evennia.utils.create import create_script
from evennia.utils.dbserialize import dbserialize
from evennia.utils.test_resources import BaseEvenniaTest, EvenniaTest
from parameterized import parameterized
class TestScript(BaseEvenniaTest):
@ -628,3 +627,51 @@ class TestOnDemandHandler(EvenniaTest):
self.assertEqual(self.handler.get_dt("daffodil", "flower"), 10000)
self.assertEqual(self.handler.get_stage("rose", "flower"), "dead")
self.assertEqual(self.handler.get_stage("daffodil", "flower"), "dead")
@mock.patch("evennia.scripts.ondemandhandler.OnDemandTask.runtime")
def test_set_dt(self, mock_runtime):
START_TIME = 0
mock_runtime.return_value = START_TIME
self.handler.batch_add(self.task1, self.task2)
for task in self.handler.tasks.values():
task.start_time = START_TIME
self.assertEqual(self.handler.get_stage("rose", "flower"), "seedling")
self.assertEqual(self.handler.get_stage("daffodil", "flower"), "seedling")
self.handler.set_dt("rose", "flower", 100)
self.handler.set_dt("daffodil", "flower", 150)
self.assertEquals(
[task.start_time for task in self.handler.tasks.values()],
[START_TIME - 100, START_TIME - 150],
)
self.assertEqual(self.handler.get_dt("rose", "flower"), 100)
self.assertEqual(self.handler.get_dt("daffodil", "flower"), 150)
self.assertEqual(self.handler.get_stage("rose", "flower"), "bud")
self.assertEqual(self.handler.get_stage("daffodil", "flower"), "wilted")
@mock.patch("evennia.scripts.ondemandhandler.OnDemandTask.runtime")
def test_set_stage(self, mock_runtime):
START_TIME = 0
mock_runtime.return_value = START_TIME
self.handler.batch_add(self.task1, self.task2)
for task in self.handler.tasks.values():
task.start_time = START_TIME
self.assertEqual(self.handler.get_stage("rose", "flower"), "seedling")
self.assertEqual(self.handler.get_stage("daffodil", "flower"), "seedling")
self.handler.set_stage("rose", "flower", "bud")
self.handler.set_stage("daffodil", "flower", "wilted")
self.assertEquals(
[task.start_time for task in self.handler.tasks.values()],
[START_TIME - 100, START_TIME - 150],
)
self.assertEqual(self.handler.get_dt("rose", "flower"), 100)
self.assertEqual(self.handler.get_dt("daffodil", "flower"), 150)
self.assertEqual(self.handler.get_stage("rose", "flower"), "bud")
self.assertEqual(self.handler.get_stage("daffodil", "flower"), "wilted")