Run black reformatter on code
This commit is contained in:
parent
4582eb4085
commit
bd3e31bf3c
178 changed files with 4511 additions and 3385 deletions
|
|
@ -165,7 +165,9 @@ def check_location(storage):
|
|||
correct = storage.location.lstrip("/")
|
||||
raise ImproperlyConfigured(
|
||||
"{}.location cannot begin with a leading slash. Found '{}'. Use '{}' instead.".format(
|
||||
storage.__class__.__name__, storage.location, correct,
|
||||
storage.__class__.__name__,
|
||||
storage.location,
|
||||
correct,
|
||||
)
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -121,7 +121,11 @@ class S3Boto3StorageTests(S3Boto3TestCase):
|
|||
|
||||
obj = self.storage.bucket.Object.return_value
|
||||
obj.upload_fileobj.assert_called_with(
|
||||
content, ExtraArgs={"ContentType": "text/plain", "ACL": self.storage.default_acl,}
|
||||
content,
|
||||
ExtraArgs={
|
||||
"ContentType": "text/plain",
|
||||
"ACL": self.storage.default_acl,
|
||||
},
|
||||
)
|
||||
|
||||
def test_storage_save_with_acl(self):
|
||||
|
|
@ -136,7 +140,11 @@ class S3Boto3StorageTests(S3Boto3TestCase):
|
|||
|
||||
obj = self.storage.bucket.Object.return_value
|
||||
obj.upload_fileobj.assert_called_with(
|
||||
content, ExtraArgs={"ContentType": "text/plain", "ACL": "private",}
|
||||
content,
|
||||
ExtraArgs={
|
||||
"ContentType": "text/plain",
|
||||
"ACL": "private",
|
||||
},
|
||||
)
|
||||
|
||||
def test_content_type(self):
|
||||
|
|
@ -151,7 +159,11 @@ class S3Boto3StorageTests(S3Boto3TestCase):
|
|||
|
||||
obj = self.storage.bucket.Object.return_value
|
||||
obj.upload_fileobj.assert_called_with(
|
||||
content, ExtraArgs={"ContentType": "image/jpeg", "ACL": self.storage.default_acl,}
|
||||
content,
|
||||
ExtraArgs={
|
||||
"ContentType": "image/jpeg",
|
||||
"ACL": self.storage.default_acl,
|
||||
},
|
||||
)
|
||||
|
||||
def test_storage_save_gzipped(self):
|
||||
|
|
@ -379,7 +391,10 @@ class S3Boto3StorageTests(S3Boto3TestCase):
|
|||
self.assertEqual(uploaded_content, written_content)
|
||||
multipart.complete.assert_called_once_with(
|
||||
MultipartUpload={
|
||||
"Parts": [{"ETag": "123", "PartNumber": 1}, {"ETag": "456", "PartNumber": 2},]
|
||||
"Parts": [
|
||||
{"ETag": "123", "PartNumber": 1},
|
||||
{"ETag": "456", "PartNumber": 2},
|
||||
]
|
||||
}
|
||||
)
|
||||
|
||||
|
|
@ -394,7 +409,10 @@ class S3Boto3StorageTests(S3Boto3TestCase):
|
|||
)
|
||||
self.storage._get_or_create_bucket("testbucketname")
|
||||
Bucket.create.assert_called_once_with(
|
||||
ACL="public-read", CreateBucketConfiguration={"LocationConstraint": "sa-east-1",}
|
||||
ACL="public-read",
|
||||
CreateBucketConfiguration={
|
||||
"LocationConstraint": "sa-east-1",
|
||||
},
|
||||
)
|
||||
|
||||
def test_auto_creating_bucket_with_acl(self):
|
||||
|
|
@ -409,22 +427,28 @@ class S3Boto3StorageTests(S3Boto3TestCase):
|
|||
)
|
||||
self.storage._get_or_create_bucket("testbucketname")
|
||||
Bucket.create.assert_called_once_with(
|
||||
ACL="public-read", CreateBucketConfiguration={"LocationConstraint": "sa-east-1",}
|
||||
ACL="public-read",
|
||||
CreateBucketConfiguration={
|
||||
"LocationConstraint": "sa-east-1",
|
||||
},
|
||||
)
|
||||
|
||||
def test_storage_exists(self):
|
||||
self.assertTrue(self.storage.exists("file.txt"))
|
||||
self.storage.connection.meta.client.head_object.assert_called_with(
|
||||
Bucket=self.storage.bucket_name, Key="file.txt",
|
||||
Bucket=self.storage.bucket_name,
|
||||
Key="file.txt",
|
||||
)
|
||||
|
||||
def test_storage_exists_false(self):
|
||||
self.storage.connection.meta.client.head_object.side_effect = ClientError(
|
||||
{"Error": {"Code": "404", "Message": "Not Found"}}, "HeadObject",
|
||||
{"Error": {"Code": "404", "Message": "Not Found"}},
|
||||
"HeadObject",
|
||||
)
|
||||
self.assertFalse(self.storage.exists("file.txt"))
|
||||
self.storage.connection.meta.client.head_object.assert_called_with(
|
||||
Bucket=self.storage.bucket_name, Key="file.txt",
|
||||
Bucket=self.storage.bucket_name,
|
||||
Key="file.txt",
|
||||
)
|
||||
|
||||
def test_storage_exists_doesnt_create_bucket(self):
|
||||
|
|
@ -445,8 +469,14 @@ class S3Boto3StorageTests(S3Boto3TestCase):
|
|||
# 4.txt
|
||||
pages = [
|
||||
{
|
||||
"CommonPrefixes": [{"Prefix": "some"}, {"Prefix": "other"},],
|
||||
"Contents": [{"Key": "2.txt"}, {"Key": "4.txt"},],
|
||||
"CommonPrefixes": [
|
||||
{"Prefix": "some"},
|
||||
{"Prefix": "other"},
|
||||
],
|
||||
"Contents": [
|
||||
{"Key": "2.txt"},
|
||||
{"Key": "4.txt"},
|
||||
],
|
||||
},
|
||||
]
|
||||
|
||||
|
|
@ -465,7 +495,14 @@ class S3Boto3StorageTests(S3Boto3TestCase):
|
|||
# some/path/1.txt
|
||||
# some/2.txt
|
||||
pages = [
|
||||
{"CommonPrefixes": [{"Prefix": "some/path"},], "Contents": [{"Key": "some/2.txt"},],},
|
||||
{
|
||||
"CommonPrefixes": [
|
||||
{"Prefix": "some/path"},
|
||||
],
|
||||
"Contents": [
|
||||
{"Key": "some/2.txt"},
|
||||
],
|
||||
},
|
||||
]
|
||||
|
||||
paginator = mock.MagicMock()
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ Building menu tests.
|
|||
"""
|
||||
|
||||
from evennia.commands.default.tests import BaseEvenniaCommandTest
|
||||
from . building_menu import BuildingMenu, CmdNoMatch
|
||||
from .building_menu import BuildingMenu, CmdNoMatch
|
||||
|
||||
|
||||
class Submenu(BuildingMenu):
|
||||
|
|
|
|||
|
|
@ -151,8 +151,9 @@ def realtime_to_gametime(secs=0, mins=0, hrs=0, days=1, weeks=1, months=1, yrs=0
|
|||
|
||||
"""
|
||||
if days <= 0 or weeks <= 0 or months <= 0:
|
||||
raise ValueError("realtime_to_gametime: days/weeks/months cannot be set <= 0, "
|
||||
"they start from 1.")
|
||||
raise ValueError(
|
||||
"realtime_to_gametime: days/weeks/months cannot be set <= 0, " "they start from 1."
|
||||
)
|
||||
|
||||
# days/weeks/months start from 1, we need to adjust them to work mathematically.
|
||||
days, weeks, months = days - 1, weeks - 1, months - 1
|
||||
|
|
|
|||
|
|
@ -31,17 +31,27 @@ class TestEventHandler(BaseEvenniaTest):
|
|||
def setUp(self):
|
||||
"""Create the event handler."""
|
||||
super().setUp()
|
||||
self.handler = create_script("evennia.contrib.base_systems.ingame_python.scripts.EventHandler")
|
||||
self.handler = create_script(
|
||||
"evennia.contrib.base_systems.ingame_python.scripts.EventHandler"
|
||||
)
|
||||
|
||||
# Copy old events if necessary
|
||||
if OLD_EVENTS:
|
||||
self.handler.ndb.events = dict(OLD_EVENTS)
|
||||
|
||||
# Alter typeclasses
|
||||
self.char1.swap_typeclass("evennia.contrib.base_systems.ingame_python.typeclasses.EventCharacter")
|
||||
self.char2.swap_typeclass("evennia.contrib.base_systems.ingame_python.typeclasses.EventCharacter")
|
||||
self.room1.swap_typeclass("evennia.contrib.base_systems.ingame_python.typeclasses.EventRoom")
|
||||
self.room2.swap_typeclass("evennia.contrib.base_systems.ingame_python.typeclasses.EventRoom")
|
||||
self.char1.swap_typeclass(
|
||||
"evennia.contrib.base_systems.ingame_python.typeclasses.EventCharacter"
|
||||
)
|
||||
self.char2.swap_typeclass(
|
||||
"evennia.contrib.base_systems.ingame_python.typeclasses.EventCharacter"
|
||||
)
|
||||
self.room1.swap_typeclass(
|
||||
"evennia.contrib.base_systems.ingame_python.typeclasses.EventRoom"
|
||||
)
|
||||
self.room2.swap_typeclass(
|
||||
"evennia.contrib.base_systems.ingame_python.typeclasses.EventRoom"
|
||||
)
|
||||
self.exit.swap_typeclass("evennia.contrib.base_systems.ingame_python.typeclasses.EventExit")
|
||||
|
||||
def tearDown(self):
|
||||
|
|
@ -253,17 +263,27 @@ class TestCmdCallback(BaseEvenniaCommandTest):
|
|||
def setUp(self):
|
||||
"""Create the callback handler."""
|
||||
super().setUp()
|
||||
self.handler = create_script("evennia.contrib.base_systems.ingame_python.scripts.EventHandler")
|
||||
self.handler = create_script(
|
||||
"evennia.contrib.base_systems.ingame_python.scripts.EventHandler"
|
||||
)
|
||||
|
||||
# Copy old events if necessary
|
||||
if OLD_EVENTS:
|
||||
self.handler.ndb.events = dict(OLD_EVENTS)
|
||||
|
||||
# Alter typeclasses
|
||||
self.char1.swap_typeclass("evennia.contrib.base_systems.ingame_python.typeclasses.EventCharacter")
|
||||
self.char2.swap_typeclass("evennia.contrib.base_systems.ingame_python.typeclasses.EventCharacter")
|
||||
self.room1.swap_typeclass("evennia.contrib.base_systems.ingame_python.typeclasses.EventRoom")
|
||||
self.room2.swap_typeclass("evennia.contrib.base_systems.ingame_python.typeclasses.EventRoom")
|
||||
self.char1.swap_typeclass(
|
||||
"evennia.contrib.base_systems.ingame_python.typeclasses.EventCharacter"
|
||||
)
|
||||
self.char2.swap_typeclass(
|
||||
"evennia.contrib.base_systems.ingame_python.typeclasses.EventCharacter"
|
||||
)
|
||||
self.room1.swap_typeclass(
|
||||
"evennia.contrib.base_systems.ingame_python.typeclasses.EventRoom"
|
||||
)
|
||||
self.room2.swap_typeclass(
|
||||
"evennia.contrib.base_systems.ingame_python.typeclasses.EventRoom"
|
||||
)
|
||||
self.exit.swap_typeclass("evennia.contrib.base_systems.ingame_python.typeclasses.EventExit")
|
||||
|
||||
def tearDown(self):
|
||||
|
|
@ -432,17 +452,27 @@ class TestDefaultCallbacks(BaseEvenniaCommandTest):
|
|||
def setUp(self):
|
||||
"""Create the callback handler."""
|
||||
super().setUp()
|
||||
self.handler = create_script("evennia.contrib.base_systems.ingame_python.scripts.EventHandler")
|
||||
self.handler = create_script(
|
||||
"evennia.contrib.base_systems.ingame_python.scripts.EventHandler"
|
||||
)
|
||||
|
||||
# Copy old events if necessary
|
||||
if OLD_EVENTS:
|
||||
self.handler.ndb.events = dict(OLD_EVENTS)
|
||||
|
||||
# Alter typeclasses
|
||||
self.char1.swap_typeclass("evennia.contrib.base_systems.ingame_python.typeclasses.EventCharacter")
|
||||
self.char2.swap_typeclass("evennia.contrib.base_systems.ingame_python.typeclasses.EventCharacter")
|
||||
self.room1.swap_typeclass("evennia.contrib.base_systems.ingame_python.typeclasses.EventRoom")
|
||||
self.room2.swap_typeclass("evennia.contrib.base_systems.ingame_python.typeclasses.EventRoom")
|
||||
self.char1.swap_typeclass(
|
||||
"evennia.contrib.base_systems.ingame_python.typeclasses.EventCharacter"
|
||||
)
|
||||
self.char2.swap_typeclass(
|
||||
"evennia.contrib.base_systems.ingame_python.typeclasses.EventCharacter"
|
||||
)
|
||||
self.room1.swap_typeclass(
|
||||
"evennia.contrib.base_systems.ingame_python.typeclasses.EventRoom"
|
||||
)
|
||||
self.room2.swap_typeclass(
|
||||
"evennia.contrib.base_systems.ingame_python.typeclasses.EventRoom"
|
||||
)
|
||||
self.exit.swap_typeclass("evennia.contrib.base_systems.ingame_python.typeclasses.EventExit")
|
||||
|
||||
def tearDown(self):
|
||||
|
|
|
|||
|
|
@ -11,7 +11,11 @@ from evennia import DefaultCharacter, DefaultExit, DefaultObject, DefaultRoom
|
|||
from evennia import ScriptDB
|
||||
from evennia.utils.utils import delay, inherits_from, lazy_property
|
||||
from evennia.contrib.base_systems.ingame_python.callbackhandler import CallbackHandler
|
||||
from evennia.contrib.base_systems.ingame_python.utils import register_events, time_event, phrase_event
|
||||
from evennia.contrib.base_systems.ingame_python.utils import (
|
||||
register_events,
|
||||
time_event,
|
||||
phrase_event,
|
||||
)
|
||||
|
||||
# Character help
|
||||
CHARACTER_CAN_DELETE = """
|
||||
|
|
|
|||
|
|
@ -173,7 +173,9 @@ def time_event(obj, event_name, number, parameters):
|
|||
"""
|
||||
seconds, usual, key = get_next_wait(parameters)
|
||||
script = create_script(
|
||||
"evennia.contrib.base_systems.ingame_python.scripts.TimeEventScript", interval=seconds, obj=obj
|
||||
"evennia.contrib.base_systems.ingame_python.scripts.TimeEventScript",
|
||||
interval=seconds,
|
||||
obj=obj,
|
||||
)
|
||||
script.key = key
|
||||
script.desc = "event on {}".format(key)
|
||||
|
|
|
|||
|
|
@ -31,8 +31,7 @@ _GUEST_ENABLED = settings.GUEST_ENABLED
|
|||
_ACCOUNT = class_from_module(settings.BASE_ACCOUNT_TYPECLASS)
|
||||
_GUEST = class_from_module(settings.BASE_GUEST_TYPECLASS)
|
||||
|
||||
_ACCOUNT_HELP = (
|
||||
"Enter a new or existing login name.")
|
||||
_ACCOUNT_HELP = "Enter a new or existing login name."
|
||||
_PASSWORD_HELP = (
|
||||
"Password should be a minimum of 8 characters (preferably longer) and "
|
||||
"can contain a mix of letters, spaces, digits and @/./+/-/_/'/, only."
|
||||
|
|
|
|||
|
|
@ -140,7 +140,7 @@ class CmdDelCom(CmdChannel):
|
|||
account_caller = True
|
||||
|
||||
def func(self):
|
||||
"""Implementing the command. """
|
||||
"""Implementing the command."""
|
||||
|
||||
caller = self.caller
|
||||
|
||||
|
|
@ -207,8 +207,7 @@ class CmdAllCom(CmdChannel):
|
|||
args = self.args
|
||||
if not args:
|
||||
subscribed, available = self.list_channels()
|
||||
self.msg(
|
||||
"\n|wAvailable channels:\n{table}")
|
||||
self.msg("\n|wAvailable channels:\n{table}")
|
||||
return
|
||||
return
|
||||
|
||||
|
|
@ -353,7 +352,7 @@ class CmdCBoot(CmdChannel):
|
|||
self.msg(string)
|
||||
return
|
||||
|
||||
success, err = self.boot_user(target, quiet='quiet' in self.switches)
|
||||
success, err = self.boot_user(target, quiet="quiet" in self.switches)
|
||||
if success:
|
||||
self.msg(f"Booted {target.key} from {channel.key}")
|
||||
logger.log_sec(
|
||||
|
|
|
|||
|
|
@ -343,7 +343,7 @@ class EvscaperoomObject(DefaultObject):
|
|||
# Evennia hooks
|
||||
|
||||
def return_appearance(self, looker, **kwargs):
|
||||
""" Could be modified per state. We generally don't worry about the
|
||||
"""Could be modified per state. We generally don't worry about the
|
||||
contents of the object by default.
|
||||
|
||||
"""
|
||||
|
|
|
|||
|
|
@ -667,8 +667,8 @@ class CraftingRecipe(CraftingRecipeBase):
|
|||
consumable_kwargs = {}
|
||||
|
||||
if location:
|
||||
tool_kwargs['location'] = location
|
||||
consumable_kwargs['location'] = location
|
||||
tool_kwargs["location"] = location
|
||||
consumable_kwargs["location"] = location
|
||||
|
||||
tool_key = tool_kwargs.pop("key", None)
|
||||
cons_key = consumable_kwargs.pop("key", None)
|
||||
|
|
@ -966,6 +966,7 @@ class CmdCraft(Command):
|
|||
things in the current location, like a furnace, windmill or anvil.
|
||||
|
||||
"""
|
||||
|
||||
key = "craft"
|
||||
locks = "cmd:all()"
|
||||
help_category = "General"
|
||||
|
|
|
|||
|
|
@ -78,6 +78,7 @@ from .crafting import craft, CraftingRecipe, CraftingValidationError
|
|||
# Sword recipe
|
||||
# ------------------------------------------------------------
|
||||
|
||||
|
||||
class PigIronRecipe(CraftingRecipe):
|
||||
"""
|
||||
Pig iron is a high-carbon result of melting iron in a blast furnace.
|
||||
|
|
@ -331,9 +332,9 @@ class SwordRecipe(_SwordSmithingBaseRecipe):
|
|||
exact_consumable_order = True
|
||||
|
||||
|
||||
#------------------------------------------------------------
|
||||
# ------------------------------------------------------------
|
||||
# Recipes for spell casting
|
||||
#------------------------------------------------------------
|
||||
# ------------------------------------------------------------
|
||||
|
||||
|
||||
class _MagicRecipe(CraftingRecipe):
|
||||
|
|
@ -348,6 +349,7 @@ class _MagicRecipe(CraftingRecipe):
|
|||
We also assume that the crafter has skills set on itself as plain Attributes.
|
||||
|
||||
"""
|
||||
|
||||
name = ""
|
||||
# all spells require a spellbook and a wand (so there!)
|
||||
tool_tags = ["spellbook", "wand"]
|
||||
|
|
@ -390,15 +392,15 @@ class _MagicRecipe(CraftingRecipe):
|
|||
skill_value = crafter.attributes.get(skill_name)
|
||||
|
||||
if skill_value is None or skill_value < min_value:
|
||||
self.msg(self.error_too_low_skill_level.format(skill_name=skill_name,
|
||||
spell=self.name))
|
||||
self.msg(
|
||||
self.error_too_low_skill_level.format(skill_name=skill_name, spell=self.name)
|
||||
)
|
||||
raise CraftingValidationError
|
||||
|
||||
# get the value of the skill to roll
|
||||
self.skill_roll_value = self.crafter.attributes.get(self.skill_roll)
|
||||
if self.skill_roll_value is None:
|
||||
self.msg(self.error_no_skill_roll.format(skill_name=self.skill_roll,
|
||||
spell=self.name))
|
||||
self.msg(self.error_no_skill_roll.format(skill_name=self.skill_roll, spell=self.name))
|
||||
raise CraftingValidationError
|
||||
|
||||
def do_craft(self, **kwargs):
|
||||
|
|
@ -446,12 +448,13 @@ class FireballRecipe(_MagicRecipe):
|
|||
need to be created to understand what they mean when used.
|
||||
|
||||
"""
|
||||
|
||||
name = "fireball"
|
||||
skill_requirements = [('firemagic', 10)] # skill 'firemagic' lvl 10 or higher
|
||||
skill_requirements = [("firemagic", 10)] # skill 'firemagic' lvl 10 or higher
|
||||
skill_roll = "firemagic"
|
||||
success_message = "A ball of flame appears!"
|
||||
desired_effects = [('target_fire_damage', 25), ('ranged_attack', -2), ('mana_cost', 12)]
|
||||
failure_effects = [('self_fire_damage', 5), ('mana_cost', 5)]
|
||||
desired_effects = [("target_fire_damage", 25), ("ranged_attack", -2), ("mana_cost", 12)]
|
||||
failure_effects = [("self_fire_damage", 5), ("mana_cost", 5)]
|
||||
|
||||
|
||||
class HealingRecipe(_MagicRecipe):
|
||||
|
|
@ -462,11 +465,12 @@ class HealingRecipe(_MagicRecipe):
|
|||
need to be created to understand what they mean.
|
||||
|
||||
"""
|
||||
|
||||
name = "heal"
|
||||
skill_requirements = [('bodymagic', 5), ("empathy", 10)]
|
||||
skill_requirements = [("bodymagic", 5), ("empathy", 10)]
|
||||
skill_roll = "bodymagic"
|
||||
success_message = "You successfully extend your healing aura."
|
||||
desired_effects = [('healing', 15), ('mana_cost', 5)]
|
||||
desired_effects = [("healing", 15), ("mana_cost", 5)]
|
||||
failure_effects = []
|
||||
|
||||
|
||||
|
|
@ -478,7 +482,8 @@ class CmdCast(Command):
|
|||
cast <spell> <target>
|
||||
|
||||
"""
|
||||
key = 'cast'
|
||||
|
||||
key = "cast"
|
||||
|
||||
def parse(self):
|
||||
"""
|
||||
|
|
@ -488,8 +493,8 @@ class CmdCast(Command):
|
|||
"""
|
||||
args = self.args.strip().lower()
|
||||
target = None
|
||||
if ' ' in args:
|
||||
self.spellname, *target = args.split(' ', 1)
|
||||
if " " in args:
|
||||
self.spellname, *target = args.split(" ", 1)
|
||||
else:
|
||||
self.spellname = args
|
||||
|
||||
|
|
@ -512,8 +517,9 @@ class CmdCast(Command):
|
|||
try:
|
||||
# if this completes without an exception, the caster will have
|
||||
# a new magic_effect set on themselves, ready to use or apply in some way.
|
||||
success, effects = craft(self.caller, self.spellname, *possible_tools,
|
||||
raise_exception=True)
|
||||
success, effects = craft(
|
||||
self.caller, self.spellname, *possible_tools, raise_exception=True
|
||||
)
|
||||
except CraftingValidationError:
|
||||
return
|
||||
except KeyError:
|
||||
|
|
@ -527,5 +533,7 @@ class CmdCast(Command):
|
|||
# (which could be yourself) by a number of health points given by the recipe.
|
||||
effect_txt = ", ".join(f"{eff[0]}({eff[1]})" for eff in effects)
|
||||
success_txt = "|gsucceeded|n" if success else "|rfailed|n"
|
||||
self.caller.msg(f"Casting the spell {self.spellname} on {self.target} {success_txt}, "
|
||||
f"causing the following effects: {effect_txt}.")
|
||||
self.caller.msg(
|
||||
f"Casting the spell {self.spellname} on {self.target} {success_txt}, "
|
||||
f"causing the following effects: {effect_txt}."
|
||||
)
|
||||
|
|
|
|||
|
|
@ -165,7 +165,7 @@ class TestCraftingRecipe(BaseEvenniaTestCase):
|
|||
pass
|
||||
|
||||
def test_error_format(self):
|
||||
"""Test the automatic error formatter """
|
||||
"""Test the automatic error formatter"""
|
||||
recipe = _MockRecipe(
|
||||
self.crafter, self.tool1, self.tool2, self.cons1, self.cons2, self.cons3
|
||||
)
|
||||
|
|
@ -428,7 +428,7 @@ class TestCraftingRecipe(BaseEvenniaTestCase):
|
|||
self.assertIsNotNone(self.tool2.pk)
|
||||
|
||||
def test_craft_tool_order__fail(self):
|
||||
"""Strict tool-order recipe fail """
|
||||
"""Strict tool-order recipe fail"""
|
||||
recipe = _MockRecipe(
|
||||
self.crafter, self.tool2, self.tool1, self.cons1, self.cons2, self.cons3
|
||||
)
|
||||
|
|
@ -451,7 +451,7 @@ class TestCraftingRecipe(BaseEvenniaTestCase):
|
|||
self.assertIsNotNone(self.tool2.pk)
|
||||
|
||||
def test_craft_cons_order__fail(self):
|
||||
"""Strict tool-order recipe fail """
|
||||
"""Strict tool-order recipe fail"""
|
||||
recipe = _MockRecipe(
|
||||
self.crafter, self.tool1, self.tool2, self.cons3, self.cons2, self.cons1
|
||||
)
|
||||
|
|
@ -653,7 +653,10 @@ class TestCraftSword(BaseEvenniaTestCase):
|
|||
|
||||
|
||||
@mock.patch("evennia.contrib.game_systems.crafting.crafting._load_recipes", new=mock.MagicMock())
|
||||
@mock.patch("evennia.contrib.game_systems.crafting.crafting._RECIPE_CLASSES", new={"testrecipe": _MockRecipe})
|
||||
@mock.patch(
|
||||
"evennia.contrib.game_systems.crafting.crafting._RECIPE_CLASSES",
|
||||
new={"testrecipe": _MockRecipe},
|
||||
)
|
||||
@override_settings(CRAFT_RECIPE_MODULES=[])
|
||||
class TestCraftCommand(BaseEvenniaCommandTest):
|
||||
"""Test the crafting command"""
|
||||
|
|
|
|||
|
|
@ -22,7 +22,9 @@ class TestGenderSub(BaseEvenniaCommandTest):
|
|||
self.assertEqual(
|
||||
gendersub._RE_GENDER_PRONOUN.sub(char._get_pronoun, txt), "Test their gender"
|
||||
)
|
||||
with patch("evennia.contrib.game_systems.gendersub.gendersub.DefaultCharacter.msg") as mock_msg:
|
||||
with patch(
|
||||
"evennia.contrib.game_systems.gendersub.gendersub.DefaultCharacter.msg"
|
||||
) as mock_msg:
|
||||
char.db.gender = "female"
|
||||
char.msg("Test |p gender")
|
||||
mock_msg.assert_called_with("Test her gender", from_obj=None, session=None)
|
||||
|
|
|
|||
|
|
@ -304,6 +304,7 @@ class TBBasicCharacter(DefaultCharacter):
|
|||
A character able to participate in turn-based combat. Has attributes for current
|
||||
and maximum HP, and access to combat commands.
|
||||
"""
|
||||
|
||||
rules = COMBAT_RULES
|
||||
|
||||
def at_object_creation(self):
|
||||
|
|
|
|||
|
|
@ -243,6 +243,7 @@ class TBEquipTurnHandler(tb_basic.TBBasicTurnHandler):
|
|||
Fights persist until only one participant is left with any HP or all
|
||||
remaining participants choose to end the combat with the 'disengage' command.
|
||||
"""
|
||||
|
||||
rules = COMBAT_RULES
|
||||
|
||||
|
||||
|
|
@ -258,6 +259,7 @@ class TBEWeapon(DefaultObject):
|
|||
A weapon which can be wielded in combat with the 'wield' command.
|
||||
|
||||
"""
|
||||
|
||||
rules = COMBAT_RULES
|
||||
|
||||
def at_object_creation(self):
|
||||
|
|
@ -513,8 +515,9 @@ class CmdWield(Command):
|
|||
weapon = self.caller.search(self.args, candidates=self.caller.contents)
|
||||
if not weapon:
|
||||
return
|
||||
if not weapon.is_typeclass("evennia.contrib.game_systems.turnbattle.tb_equip.TBEWeapon",
|
||||
exact=True):
|
||||
if not weapon.is_typeclass(
|
||||
"evennia.contrib.game_systems.turnbattle.tb_equip.TBEWeapon", exact=True
|
||||
):
|
||||
self.caller.msg("That's not a weapon!")
|
||||
# Remember to update the path to the weapon typeclass if you move this module!
|
||||
return
|
||||
|
|
@ -597,8 +600,9 @@ class CmdDon(Command):
|
|||
armor = self.caller.search(self.args, candidates=self.caller.contents)
|
||||
if not armor:
|
||||
return
|
||||
if not armor.is_typeclass("evennia.contrib.game_systems.turnbattle.tb_equip.TBEArmor",
|
||||
exact=True):
|
||||
if not armor.is_typeclass(
|
||||
"evennia.contrib.game_systems.turnbattle.tb_equip.TBEArmor", exact=True
|
||||
):
|
||||
self.caller.msg("That's not armor!")
|
||||
# Remember to update the path to the armor typeclass if you move this module!
|
||||
return
|
||||
|
|
|
|||
|
|
@ -104,7 +104,6 @@ COMBAT FUNCTIONS START HERE
|
|||
|
||||
|
||||
class ItemCombatRules(tb_basic.BasicCombatRules):
|
||||
|
||||
def get_attack(self, attacker, defender):
|
||||
"""
|
||||
Returns a value for an attack roll.
|
||||
|
|
@ -699,7 +698,7 @@ AMULET_OF_MIGHT = {
|
|||
AMULET_OF_WEAKNESS = {
|
||||
"key": "The Amulet of Weakness",
|
||||
"desc": "The one who holds this amulet can call upon its power to gain great weakness. "
|
||||
"It's not a terribly useful artifact.",
|
||||
"It's not a terribly useful artifact.",
|
||||
"item_func": "add_condition",
|
||||
"item_selfonly": True,
|
||||
"item_kwargs": {"conditions": [("Damage Down", 3), ("Accuracy Down", 3), ("Defense Down", 3)]},
|
||||
|
|
|
|||
|
|
@ -93,8 +93,8 @@ These functions also all accept **kwargs, and how these are used is specified
|
|||
in the docstring for each function.
|
||||
"""
|
||||
|
||||
class MagicCombatRules(tb_basic.BasicCombatRules):
|
||||
|
||||
class MagicCombatRules(tb_basic.BasicCombatRules):
|
||||
def spell_healing(self, caster, spell_name, targets, cost, **kwargs):
|
||||
"""
|
||||
Spell that restores HP to a target or targets.
|
||||
|
|
@ -325,11 +325,7 @@ SPELLS = {
|
|||
"attack_name": ("A jet of flame", "jets of flame"),
|
||||
"damage_range": (25, 35),
|
||||
},
|
||||
"cure wounds": {
|
||||
"spellfunc": COMBAT_RULES.spell_healing,
|
||||
"target": "anychar",
|
||||
"cost": 5
|
||||
},
|
||||
"cure wounds": {"spellfunc": COMBAT_RULES.spell_healing, "target": "anychar", "cost": 5},
|
||||
"mass cure wounds": {
|
||||
"spellfunc": COMBAT_RULES.spell_healing,
|
||||
"target": "anychar",
|
||||
|
|
@ -376,6 +372,7 @@ class TBMagicCharacter(tb_basic.TBBasicCharacter):
|
|||
and maximum HP, access to combat commands and magic.
|
||||
|
||||
"""
|
||||
|
||||
rules = COMBAT_RULES
|
||||
|
||||
def at_object_creation(self):
|
||||
|
|
|
|||
|
|
@ -122,7 +122,6 @@ COMBAT FUNCTIONS START HERE
|
|||
|
||||
|
||||
class RangedCombatRules(tb_basic.BasicCombatRules):
|
||||
|
||||
def get_attack(self, attacker, defender, attack_type):
|
||||
"""
|
||||
Returns a value for an attack roll.
|
||||
|
|
@ -154,7 +153,7 @@ class RangedCombatRules(tb_basic.BasicCombatRules):
|
|||
attack_value -= 15
|
||||
return attack_value
|
||||
|
||||
def get_defense(self, attacker, defender, attack_type='melee'):
|
||||
def get_defense(self, attacker, defender, attack_type="melee"):
|
||||
"""
|
||||
Returns a value for defense, which an attack roll must equal or exceed in order
|
||||
for an attack to hit.
|
||||
|
|
@ -284,8 +283,9 @@ class RangedCombatRules(tb_basic.BasicCombatRules):
|
|||
if thing != mover and thing != target:
|
||||
# Move away from each object closer to the target than you, if it's also closer to
|
||||
# you than you are to the target.
|
||||
if (self.get_range(mover, thing) >= self.get_range(target, thing)
|
||||
and self.get_range(mover, thing) < self.get_range(mover, target)):
|
||||
if self.get_range(mover, thing) >= self.get_range(target, thing) and self.get_range(
|
||||
mover, thing
|
||||
) < self.get_range(mover, target):
|
||||
self.distance_inc(mover, thing)
|
||||
# Move away from anything your target is engaged with
|
||||
if self.get_range(target, thing) == 0:
|
||||
|
|
@ -296,8 +296,9 @@ class RangedCombatRules(tb_basic.BasicCombatRules):
|
|||
# Then, move away from your target.
|
||||
self.distance_inc(mover, target)
|
||||
|
||||
def resolve_attack(self, attacker, defender, attack_value=None, defense_value=None,
|
||||
attack_type='melee'):
|
||||
def resolve_attack(
|
||||
self, attacker, defender, attack_value=None, defense_value=None, attack_type="melee"
|
||||
):
|
||||
"""
|
||||
Resolves an attack and outputs the result.
|
||||
|
||||
|
|
@ -496,6 +497,7 @@ class TBRangeCharacter(tb_basic.TBBasicCharacter):
|
|||
A character able to participate in turn-based combat. Has attributes for current
|
||||
and maximum HP, and access to combat commands.
|
||||
"""
|
||||
|
||||
rules = COMBAT_RULES
|
||||
|
||||
|
||||
|
|
@ -797,15 +799,17 @@ class CmdShoot(Command):
|
|||
in_melee = []
|
||||
for target in attacker.db.combat_range:
|
||||
# Object is engaged and has HP
|
||||
if (self.rules.get_range(attacker, defender) == 0
|
||||
and target.db.hp and target != self.caller):
|
||||
if (
|
||||
self.rules.get_range(attacker, defender) == 0
|
||||
and target.db.hp
|
||||
and target != self.caller
|
||||
):
|
||||
in_melee.append(target) # Add to list of targets in melee
|
||||
|
||||
if len(in_melee) > 0:
|
||||
self.caller.msg(
|
||||
"You can't shoot because there are fighters engaged with you (%s) - you need "
|
||||
"to retreat! (see: help withdraw)"
|
||||
% ", ".join(obj.key for obj in in_melee)
|
||||
"to retreat! (see: help withdraw)" % ", ".join(obj.key for obj in in_melee)
|
||||
)
|
||||
return
|
||||
|
||||
|
|
|
|||
|
|
@ -133,8 +133,9 @@ class TestTurnBattleBasicFunc(BaseEvenniaTest):
|
|||
self.assertTrue(self.defender.db.hp == 7)
|
||||
# Resolve attack
|
||||
self.defender.db.hp = 40
|
||||
tb_basic.COMBAT_RULES.resolve_attack(self.attacker, self.defender,
|
||||
attack_value=20, defense_value=10)
|
||||
tb_basic.COMBAT_RULES.resolve_attack(
|
||||
self.attacker, self.defender, attack_value=20, defense_value=10
|
||||
)
|
||||
self.assertTrue(self.defender.db.hp < 40)
|
||||
# Combat cleanup
|
||||
self.attacker.db.Combat_attribute = True
|
||||
|
|
@ -227,7 +228,9 @@ class TestTurnBattleEquipFunc(BaseEvenniaTest):
|
|||
self.assertTrue(self.defender.db.hp == 7)
|
||||
# Resolve attack
|
||||
self.defender.db.hp = 40
|
||||
tb_equip.COMBAT_RULES.resolve_attack(self.attacker, self.defender, attack_value=20, defense_value=10)
|
||||
tb_equip.COMBAT_RULES.resolve_attack(
|
||||
self.attacker, self.defender, attack_value=20, defense_value=10
|
||||
)
|
||||
self.assertTrue(self.defender.db.hp < 40)
|
||||
# Combat cleanup
|
||||
self.attacker.db.Combat_attribute = True
|
||||
|
|
@ -305,12 +308,14 @@ class TestTurnBattleRangeFunc(BaseEvenniaTest):
|
|||
initiative = tb_range.COMBAT_RULES.roll_init(self.attacker)
|
||||
self.assertTrue(initiative >= 0 and initiative <= 1000)
|
||||
# Attack roll
|
||||
attack_roll = tb_range.COMBAT_RULES.get_attack(self.attacker, self.defender,
|
||||
attack_type="test")
|
||||
attack_roll = tb_range.COMBAT_RULES.get_attack(
|
||||
self.attacker, self.defender, attack_type="test"
|
||||
)
|
||||
self.assertTrue(attack_roll >= 0 and attack_roll <= 100)
|
||||
# Defense roll
|
||||
defense_roll = tb_range.COMBAT_RULES.get_defense(self.attacker, self.defender,
|
||||
attack_type="test")
|
||||
defense_roll = tb_range.COMBAT_RULES.get_defense(
|
||||
self.attacker, self.defender, attack_type="test"
|
||||
)
|
||||
self.assertTrue(defense_roll == 50)
|
||||
# Damage roll
|
||||
damage_roll = tb_range.COMBAT_RULES.get_damage(self.attacker, self.defender)
|
||||
|
|
@ -436,8 +441,9 @@ class TestTurnBattleItemsFunc(BaseEvenniaTest):
|
|||
self.assertTrue(self.defender.db.hp == 7)
|
||||
# Resolve attack
|
||||
self.defender.db.hp = 40
|
||||
tb_items.COMBAT_RULES.resolve_attack(self.attacker, self.defender, attack_value=20,
|
||||
defense_value=10)
|
||||
tb_items.COMBAT_RULES.resolve_attack(
|
||||
self.attacker, self.defender, attack_value=20, defense_value=10
|
||||
)
|
||||
self.assertTrue(self.defender.db.hp < 40)
|
||||
# Combat cleanup
|
||||
self.attacker.db.Combat_attribute = True
|
||||
|
|
@ -555,8 +561,9 @@ class TestTurnBattleMagicFunc(BaseEvenniaTest):
|
|||
self.assertTrue(self.defender.db.hp == 7)
|
||||
# Resolve attack
|
||||
self.defender.db.hp = 40
|
||||
tb_magic.COMBAT_RULES.resolve_attack(self.attacker, self.defender, attack_value=20,
|
||||
defense_value=10)
|
||||
tb_magic.COMBAT_RULES.resolve_attack(
|
||||
self.attacker, self.defender, attack_value=20, defense_value=10
|
||||
)
|
||||
self.assertTrue(self.defender.db.hp < 40)
|
||||
# Combat cleanup
|
||||
self.attacker.db.Combat_attribute = True
|
||||
|
|
|
|||
|
|
@ -2,4 +2,3 @@
|
|||
Contribs related to moving in and manipulating the game world and grid.
|
||||
|
||||
"""
|
||||
|
||||
|
|
|
|||
|
|
@ -18,17 +18,18 @@ import random
|
|||
# A map with a temple (▲) amongst mountains (n,∩) in a forest (♣,♠) on an
|
||||
# island surrounded by water (≈). By giving no instructions for the water
|
||||
# characters we effectively skip it and create no rooms for those squares.
|
||||
EXAMPLE1_MAP = '''\
|
||||
EXAMPLE1_MAP = """\
|
||||
≈≈≈≈≈
|
||||
≈♣n♣≈
|
||||
≈∩▲∩≈
|
||||
≈♠n♠≈
|
||||
≈≈≈≈≈
|
||||
'''
|
||||
"""
|
||||
|
||||
|
||||
def example1_build_forest(x, y, **kwargs):
|
||||
'''A basic example of build instructions. Make sure to include **kwargs
|
||||
in the arguments and return an instance of the room for exit generation.'''
|
||||
"""A basic example of build instructions. Make sure to include **kwargs
|
||||
in the arguments and return an instance of the room for exit generation."""
|
||||
|
||||
# Create a room and provide a basic description.
|
||||
room = create_object(rooms.Room, key="forest" + str(x) + str(y))
|
||||
|
|
@ -42,7 +43,7 @@ def example1_build_forest(x, y, **kwargs):
|
|||
|
||||
|
||||
def example1_build_mountains(x, y, **kwargs):
|
||||
'''A room that is a little more advanced'''
|
||||
"""A room that is a little more advanced"""
|
||||
|
||||
# Create the room.
|
||||
room = create_object(rooms.Room, key="mountains" + str(x) + str(y))
|
||||
|
|
@ -68,7 +69,7 @@ def example1_build_mountains(x, y, **kwargs):
|
|||
|
||||
|
||||
def example1_build_temple(x, y, **kwargs):
|
||||
'''A unique room that does not need to be as general'''
|
||||
"""A unique room that does not need to be as general"""
|
||||
|
||||
# Create the room.
|
||||
room = create_object(rooms.Room, key="temple" + str(x) + str(y))
|
||||
|
|
@ -115,7 +116,7 @@ EXAMPLE1_LEGEND = {
|
|||
|
||||
# This is the same layout as Example 1 but included are characters for exits.
|
||||
# We can use these characters to determine which rooms should be connected.
|
||||
EXAMPLE2_MAP = '''\
|
||||
EXAMPLE2_MAP = """\
|
||||
≈ ≈ ≈ ≈ ≈
|
||||
|
||||
≈ ♣-♣-♣ ≈
|
||||
|
|
@ -125,11 +126,11 @@ EXAMPLE2_MAP = '''\
|
|||
≈ ♣-♣-♣ ≈
|
||||
|
||||
≈ ≈ ≈ ≈ ≈
|
||||
'''
|
||||
"""
|
||||
|
||||
|
||||
def example2_build_forest(x, y, **kwargs):
|
||||
'''A basic room'''
|
||||
"""A basic room"""
|
||||
# If on anything other than the first iteration - Do nothing.
|
||||
if kwargs["iteration"] > 0:
|
||||
return None
|
||||
|
|
@ -143,7 +144,7 @@ def example2_build_forest(x, y, **kwargs):
|
|||
|
||||
|
||||
def example2_build_verticle_exit(x, y, **kwargs):
|
||||
'''Creates two exits to and from the two rooms north and south.'''
|
||||
"""Creates two exits to and from the two rooms north and south."""
|
||||
# If on the first iteration - Do nothing.
|
||||
if kwargs["iteration"] == 0:
|
||||
return
|
||||
|
|
@ -164,7 +165,7 @@ def example2_build_verticle_exit(x, y, **kwargs):
|
|||
|
||||
|
||||
def example2_build_horizontal_exit(x, y, **kwargs):
|
||||
'''Creates two exits to and from the two rooms east and west.'''
|
||||
"""Creates two exits to and from the two rooms east and west."""
|
||||
# If on the first iteration - Do nothing.
|
||||
if kwargs["iteration"] == 0:
|
||||
return
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ Wilderness contrib - titeuf87, 2017
|
|||
|
||||
from .wilderness import create_wilderness # noqa
|
||||
from .wilderness import enter_wilderness # noqa
|
||||
from .wilderness import get_new_coordinates # noqa
|
||||
from .wilderness import get_new_coordinates # noqa
|
||||
from .wilderness import WildernessScript # noqa
|
||||
from .wilderness import WildernessExit # noqa
|
||||
from .wilderness import WildernessRoom # noqa
|
||||
|
|
|
|||
|
|
@ -58,6 +58,7 @@ class CmdXYZTeleport(building.CmdTeleport):
|
|||
are given, the target is a location on the XYZGrid.
|
||||
|
||||
"""
|
||||
|
||||
def _search_by_xyz(self, inp):
|
||||
inp = inp.strip("()")
|
||||
X, Y, *Z = inp.split(",", 2)
|
||||
|
|
@ -69,8 +70,10 @@ class CmdXYZTeleport(building.CmdTeleport):
|
|||
try:
|
||||
xyz = self.caller.xyz
|
||||
except AttributeError:
|
||||
self.caller.msg("Z-coordinate is also required since you are not currently "
|
||||
"in a room with a Z coordinate of its own.")
|
||||
self.caller.msg(
|
||||
"Z-coordinate is also required since you are not currently "
|
||||
"in a room with a Z coordinate of its own."
|
||||
)
|
||||
raise InterruptCommand
|
||||
else:
|
||||
Z = xyz[2]
|
||||
|
|
@ -134,9 +137,11 @@ class CmdXYZOpen(building.CmdOpen):
|
|||
|
||||
self.location = self.caller.location
|
||||
if not self.args or not self.rhs:
|
||||
self.caller.msg("Usage: open <new exit>[;alias...][:typeclass]"
|
||||
"[,<return exit>[;alias..][:typeclass]]] "
|
||||
"= <destination or (X,Y,Z)>")
|
||||
self.caller.msg(
|
||||
"Usage: open <new exit>[;alias...][:typeclass]"
|
||||
"[,<return exit>[;alias..][:typeclass]]] "
|
||||
"= <destination or (X,Y,Z)>"
|
||||
)
|
||||
raise InterruptCommand
|
||||
if not self.location:
|
||||
self.caller.msg("You cannot create an exit from a None-location.")
|
||||
|
|
@ -184,6 +189,7 @@ class CmdGoto(COMMAND_DEFAULT_CLASS):
|
|||
Builders can optionally specify a specific grid coordinate (X,Y) to go to.
|
||||
|
||||
"""
|
||||
|
||||
key = "goto"
|
||||
aliases = "path"
|
||||
help_category = "General"
|
||||
|
|
@ -207,11 +213,19 @@ class CmdGoto(COMMAND_DEFAULT_CLASS):
|
|||
|
||||
def _search_by_key_and_alias(self, inp, xyz_start):
|
||||
Z = xyz_start[2]
|
||||
candidates = list(XYZRoom.objects.filter_xyz(xyz=('*', '*', Z)))
|
||||
candidates = list(XYZRoom.objects.filter_xyz(xyz=("*", "*", Z)))
|
||||
return self.caller.search(inp, candidates=candidates)
|
||||
|
||||
def _auto_step(self, caller, session, target=None,
|
||||
xymap=None, directions=None, step_sequence=None, step=True):
|
||||
def _auto_step(
|
||||
self,
|
||||
caller,
|
||||
session,
|
||||
target=None,
|
||||
xymap=None,
|
||||
directions=None,
|
||||
step_sequence=None,
|
||||
step=True,
|
||||
):
|
||||
|
||||
path_data = caller.ndb.xy_path_data
|
||||
|
||||
|
|
@ -221,8 +235,12 @@ class CmdGoto(COMMAND_DEFAULT_CLASS):
|
|||
# stop any old task in its tracks
|
||||
path_data.task.cancel()
|
||||
path_data = caller.ndb.xy_path_data = PathData(
|
||||
target=target, xymap=xymap, directions=directions,
|
||||
step_sequence=step_sequence, task=None)
|
||||
target=target,
|
||||
xymap=xymap,
|
||||
directions=directions,
|
||||
step_sequence=step_sequence,
|
||||
task=None,
|
||||
)
|
||||
|
||||
if step and path_data:
|
||||
|
||||
|
|
@ -285,7 +303,7 @@ class CmdGoto(COMMAND_DEFAULT_CLASS):
|
|||
xymap=path_data.xymap,
|
||||
directions=directions,
|
||||
step_sequence=step_sequence,
|
||||
task=None
|
||||
task=None,
|
||||
)
|
||||
# the map can itself tell the stepper to stop the auto-step prematurely
|
||||
interrupt_node_or_link = None
|
||||
|
|
@ -301,7 +319,8 @@ class CmdGoto(COMMAND_DEFAULT_CLASS):
|
|||
|
||||
# the exit name does not need to be the same as the cardinal direction!
|
||||
exit_name, *_ = first_link.spawn_aliases.get(
|
||||
direction, current_node.direction_spawn_defaults.get(direction, ('unknown', )))
|
||||
direction, current_node.direction_spawn_defaults.get(direction, ("unknown",))
|
||||
)
|
||||
|
||||
exit_obj = caller.search(exit_name)
|
||||
if not exit_obj:
|
||||
|
|
@ -315,13 +334,15 @@ class CmdGoto(COMMAND_DEFAULT_CLASS):
|
|||
# premature stop of pathfind-step because of map node/link of interrupt type
|
||||
if hasattr(interrupt_node_or_link, "node_index"):
|
||||
message = exit_obj.destination.attributes.get(
|
||||
"xyz_path_interrupt_msg", default=self.default_xyz_path_interrupt_msg)
|
||||
"xyz_path_interrupt_msg", default=self.default_xyz_path_interrupt_msg
|
||||
)
|
||||
# we move into the node/room and then stop
|
||||
caller.execute_cmd(exit_name, session=session)
|
||||
else:
|
||||
# if the link is interrupted we don't cross it at all
|
||||
message = exit_obj.attributes.get(
|
||||
"xyz_path_interrupt_msg", default=self.default_xyz_path_interrupt_msg)
|
||||
"xyz_path_interrupt_msg", default=self.default_xyz_path_interrupt_msg
|
||||
)
|
||||
caller.msg(message)
|
||||
return
|
||||
|
||||
|
|
@ -335,7 +356,7 @@ class CmdGoto(COMMAND_DEFAULT_CLASS):
|
|||
xymap=path_data.xymap,
|
||||
directions=path_data.directions,
|
||||
step_sequence=path_data.step_sequence,
|
||||
task=delay(self.auto_step_delay, self._auto_step, caller, session)
|
||||
task=delay(self.auto_step_delay, self._auto_step, caller, session),
|
||||
)
|
||||
|
||||
def func(self):
|
||||
|
|
@ -344,7 +365,7 @@ class CmdGoto(COMMAND_DEFAULT_CLASS):
|
|||
"""
|
||||
|
||||
caller = self.caller
|
||||
goto_mode = self.cmdname == 'goto'
|
||||
goto_mode = self.cmdname == "goto"
|
||||
|
||||
# check if we have an existing path
|
||||
path_data = caller.ndb.xy_path_data
|
||||
|
|
@ -359,8 +380,7 @@ class CmdGoto(COMMAND_DEFAULT_CLASS):
|
|||
caller.msg(f"Aborted auto-walking to {target_name}.")
|
||||
return
|
||||
# goto/path-command will show current path
|
||||
current_path = list_to_string(
|
||||
[f"|w{step}|n" for step in path_data.directions])
|
||||
current_path = list_to_string([f"|w{step}|n" for step in path_data.directions])
|
||||
moving = "(moving)" if task and task.active() else ""
|
||||
caller.msg(f"Path to {target_name}{moving}: {current_path}")
|
||||
else:
|
||||
|
|
@ -405,12 +425,21 @@ class CmdGoto(COMMAND_DEFAULT_CLASS):
|
|||
xy_end = xyz_end[:2]
|
||||
directions, step_sequence = xymap.get_shortest_path(xy_start, xy_end)
|
||||
|
||||
caller.msg(f"There are {len(directions)} steps to {target.get_display_name(caller)}: "
|
||||
f"|w{list_to_string(directions, endsep='|n, and finally|w')}|n")
|
||||
caller.msg(
|
||||
f"There are {len(directions)} steps to {target.get_display_name(caller)}: "
|
||||
f"|w{list_to_string(directions, endsep='|n, and finally|w')}|n"
|
||||
)
|
||||
|
||||
# create data for display and start stepping if we used goto
|
||||
self._auto_step(caller, self.session, target=target, xymap=xymap,
|
||||
directions=directions, step_sequence=step_sequence, step=goto_mode)
|
||||
self._auto_step(
|
||||
caller,
|
||||
self.session,
|
||||
target=target,
|
||||
xymap=xymap,
|
||||
directions=directions,
|
||||
step_sequence=step_sequence,
|
||||
step=goto_mode,
|
||||
)
|
||||
|
||||
|
||||
class CmdMap(COMMAND_DEFAULT_CLASS):
|
||||
|
|
@ -424,6 +453,7 @@ class CmdMap(COMMAND_DEFAULT_CLASS):
|
|||
This is a builder-command.
|
||||
|
||||
"""
|
||||
|
||||
key = "map"
|
||||
locks = "cmd:perm(Builders)"
|
||||
|
||||
|
|
@ -453,8 +483,10 @@ class CmdMap(COMMAND_DEFAULT_CLASS):
|
|||
|
||||
xymap = xyzgrid.get_map(Z)
|
||||
if not xymap:
|
||||
self.caller.msg(f"XYMap '{Z}' is not found on the grid. Try 'map list' to see "
|
||||
"available maps/Zcoords.")
|
||||
self.caller.msg(
|
||||
f"XYMap '{Z}' is not found on the grid. Try 'map list' to see "
|
||||
"available maps/Zcoords."
|
||||
)
|
||||
return
|
||||
|
||||
self.caller.msg(ansi.raw(xymap.mapstring))
|
||||
|
|
@ -465,6 +497,7 @@ class XYZGridCmdSet(CmdSet):
|
|||
Cmdset for easily adding the above cmds to the character cmdset.
|
||||
|
||||
"""
|
||||
|
||||
key = "xyzgrid_cmdset"
|
||||
|
||||
def at_cmdset_creation(self):
|
||||
|
|
|
|||
|
|
@ -81,14 +81,13 @@ class TransitionToCave(xymap_legend.TransitionMapNode):
|
|||
into a room but only acts as a target for finding the exit's destination.
|
||||
|
||||
"""
|
||||
symbol = 'T'
|
||||
target_map_xyz = (1, 0, 'the small cave')
|
||||
|
||||
symbol = "T"
|
||||
target_map_xyz = (1, 0, "the small cave")
|
||||
|
||||
|
||||
# extends the default legend
|
||||
LEGEND_MAP1 = {
|
||||
'T': TransitionToCave
|
||||
}
|
||||
LEGEND_MAP1 = {"T": TransitionToCave}
|
||||
|
||||
|
||||
# link coordinates to rooms
|
||||
|
|
@ -96,70 +95,62 @@ PROTOTYPES_MAP1 = {
|
|||
# node/room prototypes
|
||||
(3, 0): {
|
||||
"key": "Dungeon Entrance",
|
||||
"desc": "To the east, a narrow opening leads into darkness."
|
||||
"desc": "To the east, a narrow opening leads into darkness.",
|
||||
},
|
||||
(4, 1): {
|
||||
"key": "Under the foilage of a giant tree",
|
||||
"desc": "High above the branches of a giant tree blocks out the sunlight. A slide "
|
||||
"leading down from the upper branches ends here."
|
||||
"leading down from the upper branches ends here.",
|
||||
},
|
||||
(4, 4): {
|
||||
"key": "The slide",
|
||||
"desc": "A slide leads down to the ground from here. It looks like a one-way trip."
|
||||
"desc": "A slide leads down to the ground from here. It looks like a one-way trip.",
|
||||
},
|
||||
(6, 1): {
|
||||
"key": "Thorny path",
|
||||
"desc": "To the east is a pathway of thorns. If you get through, you don't think you'll be "
|
||||
"able to get back here the same way."
|
||||
},
|
||||
(8, 1): {
|
||||
"key": "By a large tree",
|
||||
"desc": "You are standing at the root of a great tree."
|
||||
},
|
||||
(8, 3): {
|
||||
"key": "At the top of the tree",
|
||||
"desc": "You are at the top of the tree."
|
||||
"able to get back here the same way.",
|
||||
},
|
||||
(8, 1): {"key": "By a large tree", "desc": "You are standing at the root of a great tree."},
|
||||
(8, 3): {"key": "At the top of the tree", "desc": "You are at the top of the tree."},
|
||||
(3, 7): {
|
||||
"key": "Dense foilage",
|
||||
"desc": "The foilage to the east is extra dense. It will take forever to get through it."
|
||||
"desc": "The foilage to the east is extra dense. It will take forever to get through it.",
|
||||
},
|
||||
(5, 6): {
|
||||
"key": "On a huge branch",
|
||||
"desc": "To the east is a glowing light, may be a teleporter to a higher branch."
|
||||
"desc": "To the east is a glowing light, may be a teleporter to a higher branch.",
|
||||
},
|
||||
(9, 7): {
|
||||
"key": "On an enormous branch",
|
||||
"desc": "To the west is a glowing light. It may be a teleporter to a lower branch."
|
||||
"desc": "To the west is a glowing light. It may be a teleporter to a lower branch.",
|
||||
},
|
||||
(10, 8): {
|
||||
"key": "A gorgeous view",
|
||||
"desc": "The view from here is breathtaking, showing the forest stretching far and wide."
|
||||
"desc": "The view from here is breathtaking, showing the forest stretching far and wide.",
|
||||
},
|
||||
# default rooms
|
||||
('*', '*'): {
|
||||
("*", "*"): {
|
||||
"key": "Among the branches of a giant tree",
|
||||
"desc": "These branches are wide enough to easily walk on. There's green all around."
|
||||
"desc": "These branches are wide enough to easily walk on. There's green all around.",
|
||||
},
|
||||
# directional prototypes
|
||||
(3, 0, 'e'): {
|
||||
"desc": "A dark passage into the underworld."
|
||||
},
|
||||
(3, 0, "e"): {"desc": "A dark passage into the underworld."},
|
||||
}
|
||||
|
||||
for key, prot in PROTOTYPES_MAP1.items():
|
||||
if len(key) == 2:
|
||||
# we don't want to give exits the room typeclass!
|
||||
prot['prototype_parent'] = ROOM_PARENT
|
||||
prot["prototype_parent"] = ROOM_PARENT
|
||||
else:
|
||||
prot['prototype_parent'] = EXIT_PARENT
|
||||
prot["prototype_parent"] = EXIT_PARENT
|
||||
|
||||
|
||||
XYMAP_DATA_MAP1 = {
|
||||
"zcoord": "the large tree",
|
||||
"map": MAP1,
|
||||
"legend": LEGEND_MAP1,
|
||||
"prototypes": PROTOTYPES_MAP1
|
||||
"prototypes": PROTOTYPES_MAP1,
|
||||
}
|
||||
|
||||
# -------------------------------------- map2
|
||||
|
|
@ -188,14 +179,13 @@ class TransitionToLargeTree(xymap_legend.TransitionMapNode):
|
|||
into a room by only acts as a target for finding the exit's destination.
|
||||
|
||||
"""
|
||||
symbol = 'T'
|
||||
target_map_xyz = (3, 0, 'the large tree')
|
||||
|
||||
symbol = "T"
|
||||
target_map_xyz = (3, 0, "the large tree")
|
||||
|
||||
|
||||
# this extends the default legend (that defines #,-+ etc)
|
||||
LEGEND_MAP2 = {
|
||||
"T": TransitionToLargeTree
|
||||
}
|
||||
LEGEND_MAP2 = {"T": TransitionToLargeTree}
|
||||
|
||||
# prototypes for specific locations
|
||||
PROTOTYPES_MAP2 = {
|
||||
|
|
@ -203,64 +193,54 @@ PROTOTYPES_MAP2 = {
|
|||
(1, 0): {
|
||||
"key": "The entrance",
|
||||
"desc": "This is the entrance to a small cave leading into the ground. "
|
||||
"Light sifts in from the outside, while cavernous passages disappear "
|
||||
"into darkness."
|
||||
"Light sifts in from the outside, while cavernous passages disappear "
|
||||
"into darkness.",
|
||||
},
|
||||
(2, 0): {
|
||||
"key": "A gruesome sight.",
|
||||
"desc": "Something was killed here recently. The smell is unbearable."
|
||||
"desc": "Something was killed here recently. The smell is unbearable.",
|
||||
},
|
||||
(1, 1): {
|
||||
"key": "A dark pathway",
|
||||
"desc": "The path splits three ways here. To the north a faint light can be seen."
|
||||
"desc": "The path splits three ways here. To the north a faint light can be seen.",
|
||||
},
|
||||
(3, 2): {
|
||||
"key": "Stagnant water",
|
||||
"desc": "A pool of stagnant, black water dominates this small chamber. To the nortwest "
|
||||
"a faint light can be seen."
|
||||
},
|
||||
(0, 2): {
|
||||
"key": "A dark alcove",
|
||||
"desc": "This alcove is empty."
|
||||
"a faint light can be seen.",
|
||||
},
|
||||
(0, 2): {"key": "A dark alcove", "desc": "This alcove is empty."},
|
||||
(1, 2): {
|
||||
"key": "South-west corner of the atrium",
|
||||
"desc": "Sunlight sifts down into a large underground chamber. Weeds and grass sprout "
|
||||
"between the stones."
|
||||
"between the stones.",
|
||||
},
|
||||
(2, 2): {
|
||||
"key": "South-east corner of the atrium",
|
||||
"desc": "Sunlight sifts down into a large underground chamber. Weeds and grass sprout "
|
||||
"between the stones."
|
||||
"between the stones.",
|
||||
},
|
||||
(1, 3): {
|
||||
"key": "North-west corner of the atrium",
|
||||
"desc": "Sunlight sifts down into a large underground chamber. Weeds and grass sprout "
|
||||
"between the stones."
|
||||
"between the stones.",
|
||||
},
|
||||
(2, 3): {
|
||||
"key": "North-east corner of the atrium",
|
||||
"desc": "Sunlight sifts down into a large underground chamber. Weeds and grass sprout "
|
||||
"between the stones. To the east is a dark passage."
|
||||
"between the stones. To the east is a dark passage.",
|
||||
},
|
||||
(3, 3): {
|
||||
"key": "Craggy crevice",
|
||||
"desc": "This is the deepest part of the dungeon. The path shrinks away and there "
|
||||
"is no way to continue deeper."
|
||||
"is no way to continue deeper.",
|
||||
},
|
||||
# default fallback for undefined nodes
|
||||
('*', '*'): {
|
||||
"key": "A dark room",
|
||||
"desc": "A dark, but empty, room."
|
||||
},
|
||||
("*", "*"): {"key": "A dark room", "desc": "A dark, but empty, room."},
|
||||
# directional prototypes
|
||||
(1, 0, 'w'): {
|
||||
"desc": "A narrow path to the fresh air of the outside world."
|
||||
},
|
||||
(1, 0, "w"): {"desc": "A narrow path to the fresh air of the outside world."},
|
||||
# directional fallbacks for unset directions
|
||||
('*', '*', '*'): {
|
||||
"desc": "A dark passage"
|
||||
}
|
||||
("*", "*", "*"): {"desc": "A dark passage"},
|
||||
}
|
||||
|
||||
# this is required by the prototypes, but we add it all at once so we don't
|
||||
|
|
@ -268,9 +248,9 @@ PROTOTYPES_MAP2 = {
|
|||
for key, prot in PROTOTYPES_MAP2.items():
|
||||
if len(key) == 2:
|
||||
# we don't want to give exits the room typeclass!
|
||||
prot['prototype_parent'] = ROOM_PARENT
|
||||
prot["prototype_parent"] = ROOM_PARENT
|
||||
else:
|
||||
prot['prototype_parent'] = EXIT_PARENT
|
||||
prot["prototype_parent"] = EXIT_PARENT
|
||||
|
||||
|
||||
XYMAP_DATA_MAP2 = {
|
||||
|
|
@ -278,14 +258,8 @@ XYMAP_DATA_MAP2 = {
|
|||
"zcoord": "the small cave",
|
||||
"legend": LEGEND_MAP2,
|
||||
"prototypes": PROTOTYPES_MAP2,
|
||||
"options": {
|
||||
"map_visual_range": 1,
|
||||
"map_mode": 'scan'
|
||||
}
|
||||
"options": {"map_visual_range": 1, "map_mode": "scan"},
|
||||
}
|
||||
|
||||
# This is read by the parser
|
||||
XYMAP_DATA_LIST = [
|
||||
XYMAP_DATA_MAP1,
|
||||
XYMAP_DATA_MAP2
|
||||
]
|
||||
XYMAP_DATA_LIST = [XYMAP_DATA_MAP1, XYMAP_DATA_MAP2]
|
||||
|
|
|
|||
|
|
@ -160,11 +160,12 @@ _TOPICS_MAP = {
|
|||
"add": _HELP_ADD,
|
||||
"spawn": _HELP_SPAWN,
|
||||
"initpath": _HELP_INITPATH,
|
||||
"delete": _HELP_DELETE
|
||||
"delete": _HELP_DELETE,
|
||||
}
|
||||
|
||||
evennia._init()
|
||||
|
||||
|
||||
def _option_help(*suboptions):
|
||||
"""
|
||||
Show help <command> aid.
|
||||
|
|
@ -188,6 +189,7 @@ def _option_list(*suboptions):
|
|||
# override grid's logger to echo directly to console
|
||||
def _log(msg):
|
||||
print(msg)
|
||||
|
||||
xyzgrid.log = _log
|
||||
|
||||
xymap_data = xyzgrid.grid
|
||||
|
|
@ -210,7 +212,7 @@ def _option_list(*suboptions):
|
|||
if not xymap:
|
||||
print(f"No XYMap with Z='{zcoord}' was found on grid.")
|
||||
else:
|
||||
nrooms = xyzgrid.get_room(('*', '*', zcoord)).count()
|
||||
nrooms = xyzgrid.get_room(("*", "*", zcoord)).count()
|
||||
nnodes = len(xymap.node_index_map)
|
||||
print("\n" + str(repr(xymap)) + ":\n")
|
||||
checkwarning = True
|
||||
|
|
@ -218,22 +220,29 @@ def _option_list(*suboptions):
|
|||
print(f"{nrooms} / {nnodes} rooms are spawned.")
|
||||
checkwarning = False
|
||||
elif nrooms < nnodes:
|
||||
print(f"{nrooms} / {nnodes} rooms are spawned\n"
|
||||
"Note: Transitional nodes are *not* spawned (they just point \n"
|
||||
"to another map), so the 'missing room(s)' may just be from such nodes.")
|
||||
print(
|
||||
f"{nrooms} / {nnodes} rooms are spawned\n"
|
||||
"Note: Transitional nodes are *not* spawned (they just point \n"
|
||||
"to another map), so the 'missing room(s)' may just be from such nodes."
|
||||
)
|
||||
elif nrooms > nnodes:
|
||||
print(f"{nrooms} / {nnodes} rooms are spawned\n"
|
||||
"Note: Maybe some rooms were removed from map. Run 'spawn' to re-sync.")
|
||||
print(
|
||||
f"{nrooms} / {nnodes} rooms are spawned\n"
|
||||
"Note: Maybe some rooms were removed from map. Run 'spawn' to re-sync."
|
||||
)
|
||||
else:
|
||||
print(f"{nrooms} / {nnodes} rooms are spawned\n")
|
||||
|
||||
if checkwarning:
|
||||
print("Note: This check is not complete; it does not consider changed map "
|
||||
"topology\nlike relocated nodes/rooms and new/removed links/exits - this "
|
||||
"is calculated only during a spawn.")
|
||||
print(
|
||||
"Note: This check is not complete; it does not consider changed map "
|
||||
"topology\nlike relocated nodes/rooms and new/removed links/exits - this "
|
||||
"is calculated only during a spawn."
|
||||
)
|
||||
print("\nDisplayed map (as appearing in-game):\n\n" + ansi.parse_ansi(str(xymap)))
|
||||
print("\nRaw map string (including axes and invisible nodes/links):\n"
|
||||
+ str(xymap.mapstring))
|
||||
print(
|
||||
"\nRaw map string (including axes and invisible nodes/links):\n" + str(xymap.mapstring)
|
||||
)
|
||||
print(f"\nCustom map options: {xymap.options}\n")
|
||||
legend = []
|
||||
for key, node_or_link in xymap.legend.items():
|
||||
|
|
@ -260,6 +269,7 @@ def _option_add(*suboptions):
|
|||
# override grid's logger to echo directly to console
|
||||
def _log(msg):
|
||||
print(msg)
|
||||
|
||||
grid.log = _log
|
||||
|
||||
xymap_data_list = []
|
||||
|
|
@ -290,35 +300,44 @@ def _option_spawn(*suboptions):
|
|||
# override grid's logger to echo directly to console
|
||||
def _log(msg):
|
||||
print(msg)
|
||||
|
||||
grid.log = _log
|
||||
|
||||
if suboptions:
|
||||
opts = ''.join(suboptions).strip('()')
|
||||
opts = "".join(suboptions).strip("()")
|
||||
# coordinate tuple
|
||||
try:
|
||||
x, y, z = (part.strip() for part in opts.split(","))
|
||||
except ValueError:
|
||||
print("spawn coordinate must be given as (X, Y, Z) tuple, where '*' act "
|
||||
"wild cards and Z is the mapname/z-coord of the map to load.")
|
||||
print(
|
||||
"spawn coordinate must be given as (X, Y, Z) tuple, where '*' act "
|
||||
"wild cards and Z is the mapname/z-coord of the map to load."
|
||||
)
|
||||
return
|
||||
else:
|
||||
x, y, z = '*', '*', '*'
|
||||
x, y, z = "*", "*", "*"
|
||||
|
||||
if x == y == z == '*':
|
||||
inp = input("This will (re)spawn the entire grid. If it was built before, it may spawn \n"
|
||||
"new rooms or delete rooms that no longer matches the grid.\nDo you want to "
|
||||
"continue? [Y]/N? ")
|
||||
if x == y == z == "*":
|
||||
inp = input(
|
||||
"This will (re)spawn the entire grid. If it was built before, it may spawn \n"
|
||||
"new rooms or delete rooms that no longer matches the grid.\nDo you want to "
|
||||
"continue? [Y]/N? "
|
||||
)
|
||||
else:
|
||||
inp = input("This will spawn/delete objects in the database matching grid coordinates \n"
|
||||
f"({x},{y},{z}) (where '*' is a wildcard).\nDo you want to continue? [Y]/N? ")
|
||||
if inp.lower() in ('no', 'n'):
|
||||
inp = input(
|
||||
"This will spawn/delete objects in the database matching grid coordinates \n"
|
||||
f"({x},{y},{z}) (where '*' is a wildcard).\nDo you want to continue? [Y]/N? "
|
||||
)
|
||||
if inp.lower() in ("no", "n"):
|
||||
print("Aborted.")
|
||||
return
|
||||
|
||||
print("Beginner-Tutorial spawn ...")
|
||||
grid.spawn(xyz=(x, y, z))
|
||||
print("... spawn complete!\nIt's recommended to reload the server to refresh caches if this "
|
||||
"modified an existing grid.")
|
||||
print(
|
||||
"... spawn complete!\nIt's recommended to reload the server to refresh caches if this "
|
||||
"modified an existing grid."
|
||||
)
|
||||
|
||||
|
||||
def _option_initpath(*suboptions):
|
||||
|
|
@ -331,6 +350,7 @@ def _option_initpath(*suboptions):
|
|||
# override grid's logger to echo directly to console
|
||||
def _log(msg):
|
||||
print(msg)
|
||||
|
||||
grid.log = _log
|
||||
|
||||
xymaps = grid.all_maps()
|
||||
|
|
@ -354,19 +374,24 @@ def _option_delete(*suboptions):
|
|||
# override grid's logger to echo directly to console
|
||||
def _log(msg):
|
||||
print(msg)
|
||||
|
||||
grid.log = _log
|
||||
|
||||
if not suboptions:
|
||||
repl = input("WARNING: This will delete the ENTIRE Grid and wipe all rooms/exits!"
|
||||
"\nObjects/Chars inside deleted rooms will be moved to their home locations."
|
||||
"\nThis can't be undone. Are you sure you want to continue? Y/[N]? ")
|
||||
if repl.lower() not in ('yes', 'y'):
|
||||
repl = input(
|
||||
"WARNING: This will delete the ENTIRE Grid and wipe all rooms/exits!"
|
||||
"\nObjects/Chars inside deleted rooms will be moved to their home locations."
|
||||
"\nThis can't be undone. Are you sure you want to continue? Y/[N]? "
|
||||
)
|
||||
if repl.lower() not in ("yes", "y"):
|
||||
print("Aborted.")
|
||||
return
|
||||
print("Deleting grid ...")
|
||||
grid.delete()
|
||||
print("... done.\nPlease reload the server now; otherwise "
|
||||
"removed rooms may linger in cache.")
|
||||
print(
|
||||
"... done.\nPlease reload the server now; otherwise "
|
||||
"removed rooms may linger in cache."
|
||||
)
|
||||
return
|
||||
|
||||
zcoords = (part.strip() for part in suboptions)
|
||||
|
|
@ -376,21 +401,24 @@ def _option_delete(*suboptions):
|
|||
print(f"Mapname/zcoord {zcoord} is not a part of the grid.")
|
||||
err = True
|
||||
if err:
|
||||
print("Valid mapnames/zcoords are\n:", "\n ".join(
|
||||
xymap.Z for xymap in grid.all_rooms()))
|
||||
print("Valid mapnames/zcoords are\n:", "\n ".join(xymap.Z for xymap in grid.all_rooms()))
|
||||
return
|
||||
repl = input("This will delete map(s) {', '.join(zcoords)} and wipe all corresponding\n"
|
||||
"rooms/exits!"
|
||||
"\nObjects/Chars inside deleted rooms will be moved to their home locations."
|
||||
"\nThis can't be undone. Are you sure you want to continue? Y/[N]? ")
|
||||
if repl.lower() not in ('yes', 'y'):
|
||||
repl = input(
|
||||
"This will delete map(s) {', '.join(zcoords)} and wipe all corresponding\n"
|
||||
"rooms/exits!"
|
||||
"\nObjects/Chars inside deleted rooms will be moved to their home locations."
|
||||
"\nThis can't be undone. Are you sure you want to continue? Y/[N]? "
|
||||
)
|
||||
if repl.lower() not in ("yes", "y"):
|
||||
print("Aborted.")
|
||||
return
|
||||
|
||||
print("Deleting selected xymaps ...")
|
||||
grid.remove_map(*zcoords, remove_objects=True)
|
||||
print("... done.\nPlease reload the server to refresh room caches."
|
||||
"\nAlso remember to remove any links from remaining maps pointing to deleted maps.")
|
||||
print(
|
||||
"... done.\nPlease reload the server to refresh room caches."
|
||||
"\nAlso remember to remove any links from remaining maps pointing to deleted maps."
|
||||
)
|
||||
|
||||
|
||||
def xyzcommand(*args):
|
||||
|
|
@ -405,20 +433,19 @@ def xyzcommand(*args):
|
|||
|
||||
option, *suboptions = args
|
||||
|
||||
if option in ('help', 'h'):
|
||||
if option in ("help", "h"):
|
||||
_option_help(*suboptions)
|
||||
if option in ('list', 'show'):
|
||||
if option in ("list", "show"):
|
||||
_option_list(*suboptions)
|
||||
elif option == 'init':
|
||||
elif option == "init":
|
||||
_option_init(*suboptions)
|
||||
elif option == 'add':
|
||||
elif option == "add":
|
||||
_option_add(*suboptions)
|
||||
elif option == 'spawn':
|
||||
elif option == "spawn":
|
||||
_option_spawn(*suboptions)
|
||||
elif option == 'initpath':
|
||||
elif option == "initpath":
|
||||
_option_initpath(*suboptions)
|
||||
elif option == 'delete':
|
||||
elif option == "delete":
|
||||
_option_delete(*suboptions)
|
||||
else:
|
||||
print(f"Unknown option '{option}'. Use 'evennia xyzgrid help' for valid arguments.")
|
||||
|
||||
|
|
|
|||
|
|
@ -27,19 +27,19 @@ except AttributeError:
|
|||
exit_override = {}
|
||||
|
||||
room_prototype = {
|
||||
'prototype_key': 'xyz_room',
|
||||
'typeclass': 'evennia.contrib.grid.xyzgrid.xyzroom.XYZRoom',
|
||||
'prototype_tags': ("xyzroom", ),
|
||||
'key': "A room",
|
||||
'desc': "An empty room."
|
||||
"prototype_key": "xyz_room",
|
||||
"typeclass": "evennia.contrib.grid.xyzgrid.xyzroom.XYZRoom",
|
||||
"prototype_tags": ("xyzroom",),
|
||||
"key": "A room",
|
||||
"desc": "An empty room.",
|
||||
}
|
||||
room_prototype.update(room_override)
|
||||
|
||||
exit_prototype = {
|
||||
'prototype_key': 'xyz_exit',
|
||||
'typeclass': 'evennia.contrib.grid.xyzgrid.xyzroom.XYZExit',
|
||||
'prototype_tags': ("xyzexit", ),
|
||||
'desc': "An exit."
|
||||
"prototype_key": "xyz_exit",
|
||||
"typeclass": "evennia.contrib.grid.xyzgrid.xyzroom.XYZExit",
|
||||
"prototype_tags": ("xyzexit",),
|
||||
"desc": "An exit.",
|
||||
}
|
||||
exit_prototype.update(exit_override)
|
||||
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
|
|
@ -30,13 +30,15 @@ MAPSCAN = {
|
|||
|
||||
# errors for Map system
|
||||
|
||||
class MapError(RuntimeError):
|
||||
|
||||
class MapError(RuntimeError):
|
||||
def __init__(self, error="", node_or_link=None):
|
||||
prefix = ""
|
||||
if node_or_link:
|
||||
prefix = (f"{node_or_link.__class__.__name__} '{node_or_link.symbol}' "
|
||||
f"at XYZ=({node_or_link.X:g},{node_or_link.Y:g},{node_or_link.Z}) ")
|
||||
prefix = (
|
||||
f"{node_or_link.__class__.__name__} '{node_or_link.symbol}' "
|
||||
f"at XYZ=({node_or_link.X:g},{node_or_link.Y:g},{node_or_link.Z}) "
|
||||
)
|
||||
self.node_or_link = node_or_link
|
||||
self.message = f"{prefix}{error}"
|
||||
super().__init__(self.message)
|
||||
|
|
@ -52,4 +54,5 @@ class MapTransition(RuntimeWarning):
|
|||
leads to another map.
|
||||
|
||||
"""
|
||||
|
||||
pass
|
||||
|
|
|
|||
|
|
@ -104,7 +104,8 @@ try:
|
|||
except ImportError as err:
|
||||
raise ImportError(
|
||||
f"{err}\nThe XYZgrid contrib requires "
|
||||
"the SciPy package. Install with `pip install scipy'.")
|
||||
"the SciPy package. Install with `pip install scipy'."
|
||||
)
|
||||
from django.conf import settings
|
||||
from evennia.utils.utils import variable_from_module, mod_import, is_iter
|
||||
from evennia.utils import logger
|
||||
|
|
@ -122,9 +123,7 @@ _CACHE_DIR = settings.CACHE_DIR
|
|||
_LOADED_PROTOTYPES = None
|
||||
_XYZROOMCLASS = None
|
||||
|
||||
MAP_DATA_KEYS = [
|
||||
"zcoord", "map", "legend", "prototypes", "options", "module_path"
|
||||
]
|
||||
MAP_DATA_KEYS = ["zcoord", "map", "legend", "prototypes", "options", "module_path"]
|
||||
|
||||
DEFAULT_LEGEND = xymap_legend.LEGEND
|
||||
|
||||
|
|
@ -172,11 +171,11 @@ class XYMap:
|
|||
but recommended for readability!
|
||||
|
||||
"""
|
||||
mapcorner_symbol = '+'
|
||||
mapcorner_symbol = "+"
|
||||
max_pathfinding_length = 500
|
||||
empty_symbol = ' '
|
||||
empty_symbol = " "
|
||||
# we normally only accept one single character for the legend key
|
||||
legend_key_exceptions = ("\\")
|
||||
legend_key_exceptions = "\\"
|
||||
|
||||
def __init__(self, map_module_or_dict, Z="map", xyzgrid=None):
|
||||
"""
|
||||
|
|
@ -210,7 +209,9 @@ class XYMap:
|
|||
if not _LOADED_PROTOTYPES:
|
||||
# inject default prototypes, but don't override prototype-keys loaded from
|
||||
# settings, if they exist (that means the user wants to replace the defaults)
|
||||
protlib.load_module_prototypes("evennia.contrib.grid.xyzgrid.prototypes", override=False)
|
||||
protlib.load_module_prototypes(
|
||||
"evennia.contrib.grid.xyzgrid.prototypes", override=False
|
||||
)
|
||||
_LOADED_PROTOTYPES = True
|
||||
|
||||
self.Z = Z
|
||||
|
|
@ -264,7 +265,7 @@ class XYMap:
|
|||
nnodes = 0
|
||||
if self.node_index_map:
|
||||
nnodes = len(self.node_index_map)
|
||||
return (f"<XYMap(Z={self.Z}), {self.max_X + 1}x{self.max_Y + 1}, {nnodes} nodes>")
|
||||
return f"<XYMap(Z={self.Z}), {self.max_X + 1}x{self.max_Y + 1}, {nnodes} nodes>"
|
||||
|
||||
def log(self, msg):
|
||||
if self.xyzgrid:
|
||||
|
|
@ -317,34 +318,41 @@ class XYMap:
|
|||
mapdata = variable_from_module(mod, "XYMAP_DATA")
|
||||
|
||||
if not mapdata:
|
||||
raise MapError("No valid XYMAP_DATA or XYMAP_DATA_LIST could be found from "
|
||||
f"{map_module_or_dict}.")
|
||||
raise MapError(
|
||||
"No valid XYMAP_DATA or XYMAP_DATA_LIST could be found from "
|
||||
f"{map_module_or_dict}."
|
||||
)
|
||||
|
||||
# validate
|
||||
if any(key for key in mapdata if key not in MAP_DATA_KEYS):
|
||||
raise MapError(f"Mapdata has keys {list(mapdata)}, but only "
|
||||
f"keys {MAP_DATA_KEYS} are allowed.")
|
||||
raise MapError(
|
||||
f"Mapdata has keys {list(mapdata)}, but only " f"keys {MAP_DATA_KEYS} are allowed."
|
||||
)
|
||||
|
||||
for key in mapdata.get('legend', DEFAULT_LEGEND):
|
||||
for key in mapdata.get("legend", DEFAULT_LEGEND):
|
||||
if not key or len(key) > 1:
|
||||
if key not in self.legend_key_exceptions:
|
||||
raise MapError(f"Map-legend key '{key}' is invalid: All keys must "
|
||||
"be exactly one character long. Use the node/link's "
|
||||
"`.display_symbol` property to change how it is "
|
||||
"displayed.")
|
||||
if 'map' not in mapdata or not mapdata['map']:
|
||||
raise MapError(
|
||||
f"Map-legend key '{key}' is invalid: All keys must "
|
||||
"be exactly one character long. Use the node/link's "
|
||||
"`.display_symbol` property to change how it is "
|
||||
"displayed."
|
||||
)
|
||||
if "map" not in mapdata or not mapdata["map"]:
|
||||
raise MapError("No map found. Add 'map' key to map-data dict.")
|
||||
for key, prototype in mapdata.get('prototypes', {}).items():
|
||||
for key, prototype in mapdata.get("prototypes", {}).items():
|
||||
if not (is_iter(key) and (2 <= len(key) <= 3)):
|
||||
raise MapError(f"Prototype override key {key} is malformed: It must be a "
|
||||
"coordinate (X, Y) for nodes or (X, Y, direction) for links; "
|
||||
"where direction is a supported direction string ('n', 'ne', etc).")
|
||||
raise MapError(
|
||||
f"Prototype override key {key} is malformed: It must be a "
|
||||
"coordinate (X, Y) for nodes or (X, Y, direction) for links; "
|
||||
"where direction is a supported direction string ('n', 'ne', etc)."
|
||||
)
|
||||
|
||||
# store/update result
|
||||
self.Z = mapdata.get('zcoord', self.Z)
|
||||
self.mapstring = mapdata['map']
|
||||
self.prototypes = mapdata.get('prototypes', {})
|
||||
self.options = mapdata.get('options', {})
|
||||
self.Z = mapdata.get("zcoord", self.Z)
|
||||
self.mapstring = mapdata["map"]
|
||||
self.prototypes = mapdata.get("prototypes", {})
|
||||
self.options = mapdata.get("options", {})
|
||||
|
||||
# merge the custom legend onto the default legend to allow easily
|
||||
# overriding only parts of it
|
||||
|
|
@ -357,8 +365,9 @@ class XYMap:
|
|||
# nothing more to do
|
||||
continue
|
||||
# we need to load the prototype dict onto each for ease of access. Note that
|
||||
proto = protlib.search_prototype(prototype, require_single=True,
|
||||
no_db=_NO_DB_PROTOTYPES)[0]
|
||||
proto = protlib.search_prototype(
|
||||
prototype, require_single=True, no_db=_NO_DB_PROTOTYPES
|
||||
)[0]
|
||||
node_or_link_class.prototype = proto
|
||||
|
||||
def parse(self):
|
||||
|
|
@ -391,7 +400,8 @@ class XYMap:
|
|||
raise MapParserError(
|
||||
f"The mapstring must have at least two '{mapcorner_symbol}' "
|
||||
"symbols marking the upper- and bottom-left corners of the "
|
||||
"grid area.")
|
||||
"grid area."
|
||||
)
|
||||
|
||||
# find the the position (in the string as a whole) of the top-left corner-marker
|
||||
maplines = mapstring.split("\n")
|
||||
|
|
@ -406,13 +416,15 @@ class XYMap:
|
|||
# find the position (in the string as a whole) of the bottom-left corner-marker
|
||||
# this is always in a stright line down from the first marker
|
||||
botleft_marker_x, botleft_marker_y = topleft_marker_x, -1
|
||||
for botleft_marker_y, line in enumerate(maplines[topleft_marker_y + 1:]):
|
||||
for botleft_marker_y, line in enumerate(maplines[topleft_marker_y + 1 :]):
|
||||
if line.find(mapcorner_symbol) == topleft_marker_x:
|
||||
break
|
||||
if botleft_marker_y == -1:
|
||||
raise MapParserError(f"No bottom-left corner-marker ({mapcorner_symbol}) found! "
|
||||
"Make sure it lines up with the top-left corner-marker "
|
||||
f"(found at column {topleft_marker_x} of the string).")
|
||||
raise MapParserError(
|
||||
f"No bottom-left corner-marker ({mapcorner_symbol}) found! "
|
||||
"Make sure it lines up with the top-left corner-marker "
|
||||
f"(found at column {topleft_marker_x} of the string)."
|
||||
)
|
||||
# the actual coordinate is dy below the topleft marker so we need to shift
|
||||
botleft_marker_y += topleft_marker_y + 1
|
||||
|
||||
|
|
@ -443,8 +455,7 @@ class XYMap:
|
|||
mapnode_or_link_class = self.legend.get(char)
|
||||
if not mapnode_or_link_class:
|
||||
raise MapParserError(
|
||||
f"Symbol '{char}' on XY=({ix / 2:g},{iy / 2:g}) "
|
||||
"is not found in LEGEND."
|
||||
f"Symbol '{char}' on XY=({ix / 2:g},{iy / 2:g}) " "is not found in LEGEND."
|
||||
)
|
||||
if hasattr(mapnode_or_link_class, "node_index"):
|
||||
# A mapnode. Mapnodes can only be placed on even grid positions, where
|
||||
|
|
@ -454,7 +465,8 @@ class XYMap:
|
|||
raise MapParserError(
|
||||
f"Symbol '{char}' on XY=({ix / 2:g},{iy / 2:g}) marks a "
|
||||
"MapNode but is located between integer (X,Y) positions (only "
|
||||
"Links can be placed between coordinates)!")
|
||||
"Links can be placed between coordinates)!"
|
||||
)
|
||||
|
||||
# save the node to several different maps for different uses
|
||||
# in both coordinate systems
|
||||
|
|
@ -462,14 +474,17 @@ class XYMap:
|
|||
max_X, max_Y = max(max_X, iX), max(max_Y, iY)
|
||||
node_index += 1
|
||||
|
||||
xygrid[ix][iy] = XYgrid[iX][iY] = node_index_map[node_index] = \
|
||||
mapnode_or_link_class(x=ix, y=iy, Z=self.Z,
|
||||
node_index=node_index, symbol=char, xymap=self)
|
||||
xygrid[ix][iy] = XYgrid[iX][iY] = node_index_map[
|
||||
node_index
|
||||
] = mapnode_or_link_class(
|
||||
x=ix, y=iy, Z=self.Z, node_index=node_index, symbol=char, xymap=self
|
||||
)
|
||||
|
||||
else:
|
||||
# we have a link at this xygrid position (this is ok everywhere)
|
||||
xygrid[ix][iy] = mapnode_or_link_class(x=ix, y=iy, Z=self.Z, symbol=char,
|
||||
xymap=self)
|
||||
xygrid[ix][iy] = mapnode_or_link_class(
|
||||
x=ix, y=iy, Z=self.Z, symbol=char, xymap=self
|
||||
)
|
||||
|
||||
# store the symbol mapping for transition lookups
|
||||
symbol_map[char].append(xygrid[ix][iy])
|
||||
|
|
@ -499,20 +514,23 @@ class XYMap:
|
|||
node_coord = (node.X, node.Y)
|
||||
# load prototype from override, or use default
|
||||
try:
|
||||
node.prototype = flatten_prototype(self.prototypes.get(
|
||||
node_coord,
|
||||
self.prototypes.get(('*', '*'), node.prototype)),
|
||||
no_db=_NO_DB_PROTOTYPES
|
||||
node.prototype = flatten_prototype(
|
||||
self.prototypes.get(
|
||||
node_coord, self.prototypes.get(("*", "*"), node.prototype)
|
||||
),
|
||||
no_db=_NO_DB_PROTOTYPES,
|
||||
)
|
||||
except Exception as err:
|
||||
raise MapParserError(f"Room prototype malformed: {err}", node)
|
||||
# do the same for links (x, y, direction) coords
|
||||
for direction, maplink in node.first_links.items():
|
||||
try:
|
||||
maplink.prototype = flatten_prototype(self.prototypes.get(
|
||||
node_coord + (direction,),
|
||||
self.prototypes.get(('*', '*', '*'), maplink.prototype)),
|
||||
no_db=_NO_DB_PROTOTYPES
|
||||
maplink.prototype = flatten_prototype(
|
||||
self.prototypes.get(
|
||||
node_coord + (direction,),
|
||||
self.prototypes.get(("*", "*", "*"), maplink.prototype),
|
||||
),
|
||||
no_db=_NO_DB_PROTOTYPES,
|
||||
)
|
||||
except Exception as err:
|
||||
raise MapParserError(f"Exit prototype malformed: {err}", maplink)
|
||||
|
|
@ -539,8 +557,10 @@ class XYMap:
|
|||
This performs a depth-first pass down the the given dist.
|
||||
|
||||
"""
|
||||
def _scan_neighbors(start_node, points, dist=2,
|
||||
xmin=BIGVAL, ymin=BIGVAL, xmax=0, ymax=0, depth=0):
|
||||
|
||||
def _scan_neighbors(
|
||||
start_node, points, dist=2, xmin=BIGVAL, ymin=BIGVAL, xmax=0, ymax=0, depth=0
|
||||
):
|
||||
|
||||
x0, y0 = start_node.x, start_node.y
|
||||
points.append((x0, y0))
|
||||
|
|
@ -558,9 +578,15 @@ class XYMap:
|
|||
ymin, ymax = min(ymin, y), max(ymax, y)
|
||||
|
||||
points, xmin, xmax, ymin, ymax = _scan_neighbors(
|
||||
end_node, points, dist=dist,
|
||||
xmin=xmin, ymin=ymin, xmax=xmax, ymax=ymax,
|
||||
depth=depth + 1)
|
||||
end_node,
|
||||
points,
|
||||
dist=dist,
|
||||
xmin=xmin,
|
||||
ymin=ymin,
|
||||
xmax=xmax,
|
||||
ymax=ymax,
|
||||
depth=depth + 1,
|
||||
)
|
||||
|
||||
return points, xmin, xmax, ymin, ymax
|
||||
|
||||
|
|
@ -581,14 +607,16 @@ class XYMap:
|
|||
# check if the solution for this grid was already solved previously.
|
||||
|
||||
mapstr, dist_matrix, pathfinding_routes = "", None, None
|
||||
with open(self.pathfinder_baked_filename, 'rb') as fil:
|
||||
with open(self.pathfinder_baked_filename, "rb") as fil:
|
||||
try:
|
||||
mapstr, dist_matrix, pathfinding_routes = pickle.load(fil)
|
||||
except Exception:
|
||||
logger.log_trace()
|
||||
if (mapstr == self.mapstring
|
||||
and dist_matrix is not None
|
||||
and pathfinding_routes is not None):
|
||||
if (
|
||||
mapstr == self.mapstring
|
||||
and dist_matrix is not None
|
||||
and pathfinding_routes is not None
|
||||
):
|
||||
# this is important - it means the map hasn't changed so
|
||||
# we can re-use the stored data!
|
||||
self.dist_matrix = dist_matrix
|
||||
|
|
@ -606,16 +634,20 @@ class XYMap:
|
|||
|
||||
# solve using Dijkstra's algorithm
|
||||
self.dist_matrix, self.pathfinding_routes = dijkstra(
|
||||
pathfinding_matrix, directed=True,
|
||||
return_predecessors=True, limit=self.max_pathfinding_length)
|
||||
pathfinding_matrix,
|
||||
directed=True,
|
||||
return_predecessors=True,
|
||||
limit=self.max_pathfinding_length,
|
||||
)
|
||||
|
||||
if self.pathfinder_baked_filename:
|
||||
# try to cache the results
|
||||
with open(self.pathfinder_baked_filename, 'wb') as fil:
|
||||
pickle.dump((self.mapstring, self.dist_matrix, self.pathfinding_routes),
|
||||
fil, protocol=4)
|
||||
with open(self.pathfinder_baked_filename, "wb") as fil:
|
||||
pickle.dump(
|
||||
(self.mapstring, self.dist_matrix, self.pathfinding_routes), fil, protocol=4
|
||||
)
|
||||
|
||||
def spawn_nodes(self, xy=('*', '*')):
|
||||
def spawn_nodes(self, xy=("*", "*")):
|
||||
"""
|
||||
Convert the nodes of this XYMap into actual in-world rooms by spawning their
|
||||
related prototypes in the correct coordinate positions. This must be done *first*
|
||||
|
|
@ -638,12 +670,14 @@ class XYMap:
|
|||
if not _XYZROOMCLASS:
|
||||
from evennia.contrib.grid.xyzgrid.xyzroom import XYZRoom as _XYZROOMCLASS
|
||||
x, y = xy
|
||||
wildcard = '*'
|
||||
wildcard = "*"
|
||||
spawned = []
|
||||
|
||||
# find existing nodes, in case some rooms need to be removed
|
||||
map_coords = [(node.X, node.Y) for node in
|
||||
sorted(self.node_index_map.values(), key=lambda n: (n.Y, n.X))]
|
||||
map_coords = [
|
||||
(node.X, node.Y)
|
||||
for node in sorted(self.node_index_map.values(), key=lambda n: (n.Y, n.X))
|
||||
]
|
||||
for existing_room in _XYZROOMCLASS.objects.filter_xyz(xyz=(x, y, self.Z)):
|
||||
roomX, roomY, _ = existing_room.xyz
|
||||
if (roomX, roomY) not in map_coords:
|
||||
|
|
@ -657,7 +691,7 @@ class XYMap:
|
|||
spawned.append(node)
|
||||
return spawned
|
||||
|
||||
def spawn_links(self, xy=('*', '*'), nodes=None, directions=None):
|
||||
def spawn_links(self, xy=("*", "*"), nodes=None, directions=None):
|
||||
"""
|
||||
Convert links of this XYMap into actual in-game exits by spawning their related
|
||||
prototypes. It's possible to only spawn a specic exit by specifying the node and
|
||||
|
|
@ -676,7 +710,7 @@ class XYMap:
|
|||
|
||||
"""
|
||||
x, y = xy
|
||||
wildcard = '*'
|
||||
wildcard = "*"
|
||||
|
||||
if not nodes:
|
||||
nodes = sorted(self.node_index_map.values(), key=lambda n: (n.Z, n.Y, n.X))
|
||||
|
|
@ -706,8 +740,10 @@ class XYMap:
|
|||
|
||||
iX, iY = xy
|
||||
if not ((0 <= iX <= self.max_X) and (0 <= iY <= self.max_Y)):
|
||||
raise MapError(f"get_node_from_coord got coordinate {xy} which is "
|
||||
f"outside the grid size of (0,0) - ({self.max_X}, {self.max_Y}).")
|
||||
raise MapError(
|
||||
f"get_node_from_coord got coordinate {xy} which is "
|
||||
f"outside the grid size of (0,0) - ({self.max_X}, {self.max_Y})."
|
||||
)
|
||||
try:
|
||||
return self.XYgrid[iX][iY]
|
||||
except KeyError:
|
||||
|
|
@ -753,8 +789,10 @@ class XYMap:
|
|||
istartnode = startnode.node_index
|
||||
inextnode = endnode.node_index
|
||||
except AttributeError:
|
||||
raise MapError(f"Map.get_shortest_path received start/end nodes {startnode} and "
|
||||
f"{endnode}. They must both be MapNodes (not Links)")
|
||||
raise MapError(
|
||||
f"Map.get_shortest_path received start/end nodes {startnode} and "
|
||||
f"{endnode}. They must both be MapNodes (not Links)"
|
||||
)
|
||||
|
||||
if self.pathfinding_routes is None:
|
||||
self.calculate_path_matrix()
|
||||
|
|
@ -782,13 +820,18 @@ class XYMap:
|
|||
|
||||
return directions, path
|
||||
|
||||
def get_visual_range(self, xy, dist=2, mode='nodes',
|
||||
character='@',
|
||||
target=None,
|
||||
target_path_style="|y{display_symbol}|n",
|
||||
max_size=None,
|
||||
indent=0,
|
||||
return_str=True):
|
||||
def get_visual_range(
|
||||
self,
|
||||
xy,
|
||||
dist=2,
|
||||
mode="nodes",
|
||||
character="@",
|
||||
target=None,
|
||||
target_path_style="|y{display_symbol}|n",
|
||||
max_size=None,
|
||||
indent=0,
|
||||
return_str=True,
|
||||
):
|
||||
"""
|
||||
Get a part of the grid centered on a specific point and extended a certain number
|
||||
of nodes or grid points in every direction.
|
||||
|
|
@ -876,7 +919,7 @@ class XYMap:
|
|||
# nothing but ourselves or emptiness
|
||||
return character if character else self.empty_symbol
|
||||
|
||||
elif mode == 'nodes':
|
||||
elif mode == "nodes":
|
||||
# dist measures only full, reachable nodes.
|
||||
points, xmin, xmax, ymin, ymax = self._get_topology_around_coord(xy, dist=dist)
|
||||
|
||||
|
|
@ -888,7 +931,7 @@ class XYMap:
|
|||
for (ix0, iy0) in points:
|
||||
gridmap[iy0 - ymin][ix0 - xmin] = display_map[iy0][ix0]
|
||||
|
||||
elif mode == 'scan':
|
||||
elif mode == "scan":
|
||||
# scan-mode - dist measures individual grid points
|
||||
|
||||
xmin, xmax = max(0, ix - dist), min(width, ix + dist + 1)
|
||||
|
|
@ -897,8 +940,10 @@ class XYMap:
|
|||
gridmap = [line[xmin:xmax] for line in display_map[ymin:ymax]]
|
||||
|
||||
else:
|
||||
raise MapError(f"Map.get_visual_range 'mode' was '{mode}' "
|
||||
"- it must be either 'scan' or 'nodes'.")
|
||||
raise MapError(
|
||||
f"Map.get_visual_range 'mode' was '{mode}' "
|
||||
"- it must be either 'scan' or 'nodes'."
|
||||
)
|
||||
if character:
|
||||
gridmap[iyc][ixc] = character # correct indexing; it's a list of lines
|
||||
|
||||
|
|
@ -906,8 +951,7 @@ class XYMap:
|
|||
# stylize path to target
|
||||
|
||||
def _default_callable(node):
|
||||
return target_path_style.format(
|
||||
display_symbol=node.get_display_symbol())
|
||||
return target_path_style.format(display_symbol=node.get_display_symbol())
|
||||
|
||||
if callable(target_path_style):
|
||||
_target_path_style = target_path_style
|
||||
|
|
@ -916,7 +960,7 @@ class XYMap:
|
|||
|
||||
_, path = self.get_shortest_path(xy, target)
|
||||
|
||||
maxstep = dist if mode == 'nodes' else dist / 2
|
||||
maxstep = dist if mode == "nodes" else dist / 2
|
||||
nsteps = 0
|
||||
for node_or_link in path[1:]:
|
||||
if hasattr(node_or_link, "node_index"):
|
||||
|
|
|
|||
|
|
@ -14,7 +14,8 @@ try:
|
|||
except ImportError as err:
|
||||
raise ImportError(
|
||||
f"{err}\nThe XYZgrid contrib requires "
|
||||
"the SciPy package. Install with `pip install scipy'.")
|
||||
"the SciPy package. Install with `pip install scipy'."
|
||||
)
|
||||
|
||||
import uuid
|
||||
from collections import defaultdict
|
||||
|
|
@ -33,6 +34,7 @@ UUID_XYZ_NAMESPACE = uuid.uuid5(uuid.UUID(int=0), "xyzgrid")
|
|||
|
||||
# Nodes/Links
|
||||
|
||||
|
||||
class MapNode:
|
||||
"""
|
||||
This represents a 'room' node on the map. Note that the map system deals with two grids, the
|
||||
|
|
@ -62,8 +64,9 @@ class MapNode:
|
|||
for various reasons, mostly map-transitions).
|
||||
|
||||
"""
|
||||
|
||||
# symbol used to identify this link on the map
|
||||
symbol = '#'
|
||||
symbol = "#"
|
||||
# if printing this node should show another symbol. If set
|
||||
# to the empty string, use `symbol`.
|
||||
display_symbol = None
|
||||
|
|
@ -79,16 +82,16 @@ class MapNode:
|
|||
multilink = True
|
||||
# default values to use if the exit doesn't have a 'spawn_aliases' iterable
|
||||
direction_spawn_defaults = {
|
||||
'n': ('north', 'n'),
|
||||
'ne': ('northeast', 'ne', 'north-east'),
|
||||
'e': ('east', 'e'),
|
||||
'se': ('southeast', 'se', 'south-east'),
|
||||
's': ('south', 's'),
|
||||
'sw': ('southwest', 'sw', 'south-west'),
|
||||
'w': ('west', 'w'),
|
||||
'nw': ('northwest', 'nw', 'north-west'),
|
||||
'd': ('down', 'd', 'do'),
|
||||
'u': ('up', 'u'),
|
||||
"n": ("north", "n"),
|
||||
"ne": ("northeast", "ne", "north-east"),
|
||||
"e": ("east", "e"),
|
||||
"se": ("southeast", "se", "south-east"),
|
||||
"s": ("south", "s"),
|
||||
"sw": ("southwest", "sw", "south-west"),
|
||||
"w": ("west", "w"),
|
||||
"nw": ("northwest", "nw", "north-west"),
|
||||
"d": ("down", "d", "do"),
|
||||
"u": ("up", "u"),
|
||||
}
|
||||
|
||||
def __init__(self, x, y, Z, node_index=0, symbol=None, xymap=None):
|
||||
|
|
@ -202,7 +205,9 @@ class MapNode:
|
|||
if first_step_name in self.closest_neighbor_names:
|
||||
raise MapParserError(
|
||||
f"has more than one outgoing direction '{first_step_name}'. "
|
||||
"All directions out of a node must be unique.", self)
|
||||
"All directions out of a node must be unique.",
|
||||
self,
|
||||
)
|
||||
self.closest_neighbor_names[first_step_name] = direction
|
||||
|
||||
node_index = end_node.node_index
|
||||
|
|
@ -215,8 +220,9 @@ class MapNode:
|
|||
# used for building the shortest path. Note that we store the
|
||||
# aliased link directions here, for quick display by the
|
||||
# shortest-route solver
|
||||
shortest_route = self.shortest_route_to_node.get(
|
||||
node_index, ("", [], BIGVAL))[2]
|
||||
shortest_route = self.shortest_route_to_node.get(node_index, ("", [], BIGVAL))[
|
||||
2
|
||||
]
|
||||
if weight < shortest_route:
|
||||
self.shortest_route_to_node[node_index] = (first_step_name, steps, weight)
|
||||
|
||||
|
|
@ -280,11 +286,9 @@ class MapNode:
|
|||
str or tuple: The key of the spawned exit, or a tuple (key, alias, alias, ...)
|
||||
|
||||
"""
|
||||
key, *aliases = (
|
||||
self.first_links[direction]
|
||||
.spawn_aliases.get(
|
||||
direction, self.direction_spawn_defaults.get(
|
||||
direction, ('unknown', ))))
|
||||
key, *aliases = self.first_links[direction].spawn_aliases.get(
|
||||
direction, self.direction_spawn_defaults.get(direction, ("unknown",))
|
||||
)
|
||||
if return_aliases:
|
||||
return (key, *aliases)
|
||||
return key
|
||||
|
|
@ -313,28 +317,24 @@ class MapNode:
|
|||
nodeobj = NodeTypeclass.objects.get_xyz(xyz=xyz)
|
||||
except django_exceptions.ObjectDoesNotExist:
|
||||
# create a new entity with proper coordinates etc
|
||||
tclass = self.prototype['typeclass']
|
||||
tclass = (f' ({tclass})'
|
||||
if tclass != 'evennia.contrib.grid.xyzgrid.xyzroom.XYZRoom'
|
||||
else '')
|
||||
self.log(f" spawning room at xyz={xyz}{tclass}")
|
||||
nodeobj, err = NodeTypeclass.create(
|
||||
self.prototype.get('key', 'An empty room'),
|
||||
xyz=xyz
|
||||
tclass = self.prototype["typeclass"]
|
||||
tclass = (
|
||||
f" ({tclass})" if tclass != "evennia.contrib.grid.xyzgrid.xyzroom.XYZRoom" else ""
|
||||
)
|
||||
self.log(f" spawning room at xyz={xyz}{tclass}")
|
||||
nodeobj, err = NodeTypeclass.create(self.prototype.get("key", "An empty room"), xyz=xyz)
|
||||
if err:
|
||||
raise RuntimeError(err)
|
||||
else:
|
||||
self.log(f" updating existing room (if changed) at xyz={xyz}")
|
||||
|
||||
if not self.prototype.get('prototype_key'):
|
||||
if not self.prototype.get("prototype_key"):
|
||||
# make sure there is a prototype_key in prototype
|
||||
self.prototype['prototype_key'] = self.generate_prototype_key()
|
||||
self.prototype["prototype_key"] = self.generate_prototype_key()
|
||||
|
||||
# apply prototype to node. This will not override the XYZ tags since
|
||||
# these are not in the prototype and exact=False
|
||||
spawner.batch_update_objects_with_prototype(
|
||||
self.prototype, objects=[nodeobj], exact=False)
|
||||
spawner.batch_update_objects_with_prototype(self.prototype, objects=[nodeobj], exact=False)
|
||||
|
||||
def spawn_links(self, directions=None):
|
||||
"""
|
||||
|
|
@ -364,9 +364,9 @@ class MapNode:
|
|||
for direction, link in self.first_links.items():
|
||||
|
||||
key, *aliases = self.get_exit_spawn_name(direction)
|
||||
if not link.prototype.get('prototype_key'):
|
||||
if not link.prototype.get("prototype_key"):
|
||||
# generate a deterministic prototype_key if it doesn't exist
|
||||
link.prototype['prototype_key'] = self.generate_prototype_key()
|
||||
link.prototype["prototype_key"] = self.generate_prototype_key()
|
||||
maplinks[key.lower()] = (key, aliases, direction, link)
|
||||
|
||||
# remove duplicates
|
||||
|
|
@ -380,8 +380,7 @@ class MapNode:
|
|||
|
||||
# we need to search for exits in all directions since some
|
||||
# may have been removed since last sync
|
||||
linkobjs = {exi.db_key.lower(): exi
|
||||
for exi in ExitTypeclass.objects.filter_xyz(xyz=xyz)}
|
||||
linkobjs = {exi.db_key.lower(): exi for exi in ExitTypeclass.objects.filter_xyz(xyz=xyz)}
|
||||
|
||||
# figure out if the topology changed between grid and map (will always
|
||||
# build all exits first run)
|
||||
|
|
@ -411,16 +410,19 @@ class MapNode:
|
|||
raise RuntimeError(err)
|
||||
linkobjs[key.lower()] = exi
|
||||
prot = maplinks[key.lower()][3].prototype
|
||||
tclass = prot['typeclass']
|
||||
tclass = (f' ({tclass})'
|
||||
if tclass != 'evennia.contrib.grid.xyzgrid.xyzroom.XYZExit'
|
||||
else '')
|
||||
tclass = prot["typeclass"]
|
||||
tclass = (
|
||||
f" ({tclass})"
|
||||
if tclass != "evennia.contrib.grid.xyzgrid.xyzroom.XYZExit"
|
||||
else ""
|
||||
)
|
||||
self.log(f" spawning/updating exit xyz={xyz}, direction={key}{tclass}")
|
||||
|
||||
# apply prototypes to catch any changes
|
||||
for key, linkobj in linkobjs.items():
|
||||
spawner.batch_update_objects_with_prototype(
|
||||
maplinks[key.lower()][3].prototype, objects=[linkobj], exact=False)
|
||||
maplinks[key.lower()][3].prototype, objects=[linkobj], exact=False
|
||||
)
|
||||
|
||||
def unspawn(self):
|
||||
"""
|
||||
|
|
@ -466,8 +468,9 @@ class TransitionMapNode(MapNode):
|
|||
actual rooms (`#`) on the other map (NOT to the `T`s)!
|
||||
|
||||
"""
|
||||
symbol = 'T'
|
||||
display_symbol = ' '
|
||||
|
||||
symbol = "T"
|
||||
display_symbol = " "
|
||||
# X,Y,Z coordinates of target node
|
||||
taget_map_xyz = (None, None, None)
|
||||
|
||||
|
|
@ -477,10 +480,13 @@ class TransitionMapNode(MapNode):
|
|||
the exit to this node (since the prototype is None, this node itself will not be built).
|
||||
|
||||
"""
|
||||
if any(True for coord in self.target_map_xyz if coord in (None, 'unset')):
|
||||
raise MapParserError(f"(Z={self.xymap.Z}) has not defined its "
|
||||
"`.target_map_xyz` property. It must point "
|
||||
"to another valid xymap (Z coordinate).", self)
|
||||
if any(True for coord in self.target_map_xyz if coord in (None, "unset")):
|
||||
raise MapParserError(
|
||||
f"(Z={self.xymap.Z}) has not defined its "
|
||||
"`.target_map_xyz` property. It must point "
|
||||
"to another valid xymap (Z coordinate).",
|
||||
self,
|
||||
)
|
||||
|
||||
return self.target_map_xyz
|
||||
|
||||
|
|
@ -548,6 +554,7 @@ class MapLink:
|
|||
`node.get_exit_spawn_name(direction)`
|
||||
|
||||
"""
|
||||
|
||||
# symbol for identifying this link on the map
|
||||
symbol = ""
|
||||
# if `None`, use .symbol
|
||||
|
|
@ -661,7 +668,9 @@ class MapLink:
|
|||
return None, 0, None
|
||||
raise MapParserError(
|
||||
f"was connected to from the direction {start_direction}, but "
|
||||
"is not set up to link in that direction.", self)
|
||||
"is not set up to link in that direction.",
|
||||
self,
|
||||
)
|
||||
|
||||
# note that if `get_direction` returns an unknown direction, this will be equivalent
|
||||
# to pointing to an empty location, which makes sense
|
||||
|
|
@ -674,8 +683,7 @@ class MapLink:
|
|||
next_target = self.at_empty_target(start_direction, end_direction)
|
||||
|
||||
if not next_target:
|
||||
raise MapParserError(
|
||||
f"points to empty space in the direction {end_direction}!", self)
|
||||
raise MapParserError(f"points to empty space in the direction {end_direction}!", self)
|
||||
|
||||
_weight += self.get_weight(start_direction, _weight)
|
||||
if _steps is None:
|
||||
|
|
@ -688,13 +696,16 @@ class MapLink:
|
|||
return (
|
||||
next_target,
|
||||
_weight / max(1, _linklen) if self.average_long_link_weights else _weight,
|
||||
_steps
|
||||
_steps,
|
||||
)
|
||||
else:
|
||||
# we hit another link. Progress recursively.
|
||||
return next_target.traverse(
|
||||
REVERSE_DIRECTIONS.get(end_direction, end_direction),
|
||||
_weight=_weight, _linklen=_linklen + 1, _steps=_steps)
|
||||
_weight=_weight,
|
||||
_linklen=_linklen + 1,
|
||||
_steps=_steps,
|
||||
)
|
||||
|
||||
def get_linked_neighbors(self, directions=None):
|
||||
"""
|
||||
|
|
@ -720,8 +731,7 @@ class MapLink:
|
|||
# there is is something there, we need to check if it is either
|
||||
# a map node or a link connecting in our direction
|
||||
node_or_link = xygrid[end_x][end_y]
|
||||
if (node_or_link.multilink
|
||||
or node_or_link.get_direction(direction)):
|
||||
if node_or_link.multilink or node_or_link.get_direction(direction):
|
||||
links[direction] = node_or_link
|
||||
return links
|
||||
|
||||
|
|
@ -845,7 +855,8 @@ class SmartRerouterMapLink(MapLink):
|
|||
for direction in unhandled_links_copy:
|
||||
if REVERSE_DIRECTIONS[direction] in unhandled_links_copy:
|
||||
directions[direction] = REVERSE_DIRECTIONS[
|
||||
unhandled_links.pop(unhandled_links.index(direction))]
|
||||
unhandled_links.pop(unhandled_links.index(direction))
|
||||
]
|
||||
|
||||
# check if we have any non-cross-through paths left to handle
|
||||
n_unhandled = len(unhandled_links)
|
||||
|
|
@ -856,7 +867,8 @@ class SmartRerouterMapLink(MapLink):
|
|||
if n_unhandled != 2:
|
||||
links = ", ".join(unhandled_links)
|
||||
raise MapParserError(
|
||||
f"cannot determine how to connect in/out directions {links}.", self)
|
||||
f"cannot determine how to connect in/out directions {links}.", self
|
||||
)
|
||||
|
||||
directions[unhandled_links[0]] = unhandled_links[1]
|
||||
directions[unhandled_links[1]] = unhandled_links[0]
|
||||
|
|
@ -865,6 +877,7 @@ class SmartRerouterMapLink(MapLink):
|
|||
|
||||
return self.directions.get(start_direction)
|
||||
|
||||
|
||||
class SmartTeleporterMapLink(MapLink):
|
||||
"""
|
||||
The teleport link works by connecting to nowhere - and will then continue
|
||||
|
|
@ -889,10 +902,11 @@ class SmartTeleporterMapLink(MapLink):
|
|||
-#-t-# - invalid, only one connected link is allowed.
|
||||
|
||||
"""
|
||||
symbol = 't'
|
||||
|
||||
symbol = "t"
|
||||
# usually invisible
|
||||
display_symbol = ' '
|
||||
direction_name = 'teleport'
|
||||
display_symbol = " "
|
||||
direction_name = "teleport"
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
|
|
@ -932,7 +946,9 @@ class SmartTeleporterMapLink(MapLink):
|
|||
if len(found_teleporters) > 1:
|
||||
raise MapParserError(
|
||||
"found too many matching teleporters (must be exactly one more): "
|
||||
f"{found_teleporters}", self)
|
||||
f"{found_teleporters}",
|
||||
self,
|
||||
)
|
||||
|
||||
other_teleporter = found_teleporters[0]
|
||||
# link the two so we don't need to scan again for the other one
|
||||
|
|
@ -952,9 +968,10 @@ class SmartTeleporterMapLink(MapLink):
|
|||
if len(neighbors) != 1:
|
||||
raise MapParserError("must have exactly one link connected to it.", self)
|
||||
direction, link = next(iter(neighbors.items()))
|
||||
if hasattr(link, 'node_index'):
|
||||
raise MapParserError("can only connect to a Link. Found {link} in "
|
||||
"direction {direction}.", self)
|
||||
if hasattr(link, "node_index"):
|
||||
raise MapParserError(
|
||||
"can only connect to a Link. Found {link} in " "direction {direction}.", self
|
||||
)
|
||||
# the string 'teleport' will not be understood by the traverser, leading to
|
||||
# this being interpreted as an empty target and the `at_empty_target`
|
||||
# hook firing when trying to traverse this link.
|
||||
|
|
@ -962,12 +979,10 @@ class SmartTeleporterMapLink(MapLink):
|
|||
if start_direction == direction_name:
|
||||
# called while traversing another teleport
|
||||
# - we must make sure we can always access/leave the teleport.
|
||||
self.directions = {direction_name: direction,
|
||||
direction: direction_name}
|
||||
self.directions = {direction_name: direction, direction: direction_name}
|
||||
else:
|
||||
# called while traversing a normal link
|
||||
self.directions = {start_direction: direction_name,
|
||||
direction_name: direction}
|
||||
self.directions = {start_direction: direction_name, direction_name: direction}
|
||||
|
||||
return self.directions.get(start_direction)
|
||||
|
||||
|
|
@ -1016,6 +1031,7 @@ class SmartMapLink(MapLink):
|
|||
# |
|
||||
|
||||
"""
|
||||
|
||||
multilink = True
|
||||
|
||||
def get_direction(self, start_direction):
|
||||
|
|
@ -1027,8 +1043,11 @@ class SmartMapLink(MapLink):
|
|||
if not self.directions:
|
||||
directions = {}
|
||||
neighbors = self.get_linked_neighbors()
|
||||
nodes = [direction for direction, neighbor in neighbors.items()
|
||||
if hasattr(neighbor, 'node_index')]
|
||||
nodes = [
|
||||
direction
|
||||
for direction, neighbor in neighbors.items()
|
||||
if hasattr(neighbor, "node_index")
|
||||
]
|
||||
|
||||
if len(nodes) == 2:
|
||||
# prefer link to these two nodes
|
||||
|
|
@ -1042,7 +1061,9 @@ class SmartMapLink(MapLink):
|
|||
"must have exactly two connections - either directly to "
|
||||
"two nodes or connecting directly to one node and with exactly one other "
|
||||
f"link direction. The neighbor(s) in directions {list(neighbors.keys())} do "
|
||||
"not fulfill these criteria.", self)
|
||||
"not fulfill these criteria.",
|
||||
self,
|
||||
)
|
||||
|
||||
self.directions = directions
|
||||
return self.directions.get(start_direction)
|
||||
|
|
@ -1071,20 +1092,26 @@ class InvisibleSmartMapLink(SmartMapLink):
|
|||
# this allows for normal movement directions even if the invisible-node
|
||||
# is marked with a different symbol.
|
||||
direction_aliases = {
|
||||
'n': 'n', 'ne': 'ne', 'e': 'e', 'se': 'se',
|
||||
's': 's', 'sw': 'sw', 'w': 'w', 'nw': 'nw'
|
||||
"n": "n",
|
||||
"ne": "ne",
|
||||
"e": "e",
|
||||
"se": "se",
|
||||
"s": "s",
|
||||
"sw": "sw",
|
||||
"w": "w",
|
||||
"nw": "nw",
|
||||
}
|
||||
|
||||
# replace current link position with what the smart links "should" look like
|
||||
display_symbol_aliases = {
|
||||
(('n', 's'), ('s', 'n')): '|',
|
||||
(('n', 's'),): 'v',
|
||||
(('s', 'n')): '^',
|
||||
(('e', 'w'), ('w', 'e')): '-',
|
||||
(('e', 'w'),): '>',
|
||||
(('w', 'e'),): '<',
|
||||
(('nw', 'se'), ('sw', 'ne')): '\\',
|
||||
(('ne', 'sw'), ('sw', 'ne')): '/',
|
||||
(("n", "s"), ("s", "n")): "|",
|
||||
(("n", "s"),): "v",
|
||||
(("s", "n")): "^",
|
||||
(("e", "w"), ("w", "e")): "-",
|
||||
(("e", "w"),): ">",
|
||||
(("w", "e"),): "<",
|
||||
(("nw", "se"), ("sw", "ne")): "\\",
|
||||
(("ne", "sw"), ("sw", "ne")): "/",
|
||||
}
|
||||
|
||||
def get_display_symbol(self):
|
||||
|
|
@ -1098,12 +1125,10 @@ class InvisibleSmartMapLink(SmartMapLink):
|
|||
"""
|
||||
if not hasattr(self, "_cached_display_symbol"):
|
||||
legend = self.xymap.legend
|
||||
default_symbol = (
|
||||
self.symbol if self.display_symbol is None else self.display_symbol)
|
||||
default_symbol = self.symbol if self.display_symbol is None else self.display_symbol
|
||||
self._cached_display_symbol = default_symbol
|
||||
|
||||
dirtuple = tuple((key, self.directions[key])
|
||||
for key in sorted(self.directions.keys()))
|
||||
dirtuple = tuple((key, self.directions[key]) for key in sorted(self.directions.keys()))
|
||||
|
||||
replacement_symbol = self.display_symbol_aliases.get(dirtuple, default_symbol)
|
||||
|
||||
|
|
@ -1112,16 +1137,19 @@ class InvisibleSmartMapLink(SmartMapLink):
|
|||
if node_or_link_class:
|
||||
# initiate class in the current location and run get_display_symbol
|
||||
# to get what it would show.
|
||||
self._cached_display_symbol = (
|
||||
node_or_link_class(self.x, self.y, self.Z).get_display_symbol())
|
||||
self._cached_display_symbol = node_or_link_class(
|
||||
self.x, self.y, self.Z
|
||||
).get_display_symbol()
|
||||
return self._cached_display_symbol
|
||||
|
||||
|
||||
# ----------------------------------
|
||||
# Default nodes and link classes
|
||||
|
||||
|
||||
class BasicMapNode(MapNode):
|
||||
"""A map node/room"""
|
||||
|
||||
symbol = "#"
|
||||
prototype = "xyz_room"
|
||||
|
||||
|
|
@ -1129,20 +1157,25 @@ class BasicMapNode(MapNode):
|
|||
class InterruptMapNode(MapNode):
|
||||
"""A point of interest node/room. Pathfinder will ignore but auto-stepper will
|
||||
stop here if passing through. Beginner-Tutorial from here is fine."""
|
||||
|
||||
symbol = "I"
|
||||
display_symbol = "#"
|
||||
interrupt_path = True
|
||||
prototype = "xyz_room"
|
||||
|
||||
|
||||
class MapTransitionNode(TransitionMapNode):
|
||||
"""Transition-target node to other map. This is not actually spawned in-game."""
|
||||
|
||||
symbol = "T"
|
||||
display_symbol = " "
|
||||
prototype = None # important to leave None!
|
||||
target_map_xyz = (None, None, None) # must be set manually
|
||||
|
||||
|
||||
class NSMapLink(MapLink):
|
||||
"""Two-way, North-South link"""
|
||||
|
||||
symbol = "|"
|
||||
display_symbol = "||"
|
||||
directions = {"n": "s", "s": "n"}
|
||||
|
|
@ -1151,6 +1184,7 @@ class NSMapLink(MapLink):
|
|||
|
||||
class EWMapLink(MapLink):
|
||||
"""Two-way, East-West link"""
|
||||
|
||||
symbol = "-"
|
||||
directions = {"e": "w", "w": "e"}
|
||||
prototype = "xyz_exit"
|
||||
|
|
@ -1158,6 +1192,7 @@ class EWMapLink(MapLink):
|
|||
|
||||
class NESWMapLink(MapLink):
|
||||
"""Two-way, NorthWest-SouthWest link"""
|
||||
|
||||
symbol = "/"
|
||||
directions = {"ne": "sw", "sw": "ne"}
|
||||
prototype = "xyz_exit"
|
||||
|
|
@ -1165,6 +1200,7 @@ class NESWMapLink(MapLink):
|
|||
|
||||
class SENWMapLink(MapLink):
|
||||
"""Two-way, SouthEast-NorthWest link"""
|
||||
|
||||
symbol = "\\"
|
||||
directions = {"se": "nw", "nw": "se"}
|
||||
prototype = "xyz_exit"
|
||||
|
|
@ -1172,22 +1208,23 @@ class SENWMapLink(MapLink):
|
|||
|
||||
class PlusMapLink(MapLink):
|
||||
"""Two-way, crossing North-South and East-West links"""
|
||||
|
||||
symbol = "+"
|
||||
directions = {"s": "n", "n": "s",
|
||||
"e": "w", "w": "e"}
|
||||
directions = {"s": "n", "n": "s", "e": "w", "w": "e"}
|
||||
prototype = "xyz_exit"
|
||||
|
||||
|
||||
class CrossMapLink(MapLink):
|
||||
"""Two-way, crossing NorthEast-SouthWest and SouthEast-NorthWest links"""
|
||||
|
||||
symbol = "x"
|
||||
directions = {"ne": "sw", "sw": "ne",
|
||||
"se": "nw", "nw": "se"}
|
||||
directions = {"ne": "sw", "sw": "ne", "se": "nw", "nw": "se"}
|
||||
prototype = "xyz_exit"
|
||||
|
||||
|
||||
class NSOneWayMapLink(MapLink):
|
||||
"""One-way North-South link"""
|
||||
|
||||
symbol = "v"
|
||||
directions = {"n": "s"}
|
||||
prototype = "xyz_exit"
|
||||
|
|
@ -1195,6 +1232,7 @@ class NSOneWayMapLink(MapLink):
|
|||
|
||||
class SNOneWayMapLink(MapLink):
|
||||
"""One-way South-North link"""
|
||||
|
||||
symbol = "^"
|
||||
directions = {"s": "n"}
|
||||
prototype = "xyz_exit"
|
||||
|
|
@ -1202,6 +1240,7 @@ class SNOneWayMapLink(MapLink):
|
|||
|
||||
class EWOneWayMapLink(MapLink):
|
||||
"""One-way East-West link"""
|
||||
|
||||
symbol = "<"
|
||||
directions = {"e": "w"}
|
||||
prototype = "xyz_exit"
|
||||
|
|
@ -1209,6 +1248,7 @@ class EWOneWayMapLink(MapLink):
|
|||
|
||||
class WEOneWayMapLink(MapLink):
|
||||
"""One-way West-East link"""
|
||||
|
||||
symbol = ">"
|
||||
directions = {"w": "e"}
|
||||
prototype = "xyz_exit"
|
||||
|
|
@ -1216,21 +1256,39 @@ class WEOneWayMapLink(MapLink):
|
|||
|
||||
class UpMapLink(SmartMapLink):
|
||||
"""Up direction. Note that this stays on the same z-coord so it's a 'fake' up."""
|
||||
symbol = 'u'
|
||||
|
||||
symbol = "u"
|
||||
|
||||
# all movement over this link is 'up', regardless of where on the xygrid we move.
|
||||
direction_aliases = {'n': symbol, 'ne': symbol, 'e': symbol, 'se': symbol,
|
||||
's': symbol, 'sw': symbol, 'w': symbol, 'nw': symbol}
|
||||
direction_aliases = {
|
||||
"n": symbol,
|
||||
"ne": symbol,
|
||||
"e": symbol,
|
||||
"se": symbol,
|
||||
"s": symbol,
|
||||
"sw": symbol,
|
||||
"w": symbol,
|
||||
"nw": symbol,
|
||||
}
|
||||
spawn_aliases = {direction: ("up", "u") for direction in direction_aliases}
|
||||
prototype = "xyz_exit"
|
||||
|
||||
|
||||
class DownMapLink(UpMapLink):
|
||||
"""Down direction. Note that this stays on the same z-coord, so it's a 'fake' down."""
|
||||
symbol = 'd'
|
||||
|
||||
symbol = "d"
|
||||
# all movement over this link is 'down', regardless of where on the xygrid we move.
|
||||
direction_aliases = {'n': symbol, 'ne': symbol, 'e': symbol, 'se': symbol,
|
||||
's': symbol, 'sw': symbol, 'w': symbol, 'nw': symbol}
|
||||
direction_aliases = {
|
||||
"n": symbol,
|
||||
"ne": symbol,
|
||||
"e": symbol,
|
||||
"se": symbol,
|
||||
"s": symbol,
|
||||
"sw": symbol,
|
||||
"w": symbol,
|
||||
"nw": symbol,
|
||||
}
|
||||
spawn_aliases = {direction: ("down", "d") for direction in direction_aliases}
|
||||
prototype = "xyz_exit"
|
||||
|
||||
|
|
@ -1238,6 +1296,7 @@ class DownMapLink(UpMapLink):
|
|||
class InterruptMapLink(InvisibleSmartMapLink):
|
||||
"""A (still passable) link. Pathfinder will treat this as any link, but auto-stepper
|
||||
will always abort before crossing this link - so this must be crossed manually."""
|
||||
|
||||
symbol = "i"
|
||||
interrupt_path = True
|
||||
prototype = "xyz_exit"
|
||||
|
|
@ -1250,14 +1309,24 @@ class BlockedMapLink(InvisibleSmartMapLink):
|
|||
link in any paths.
|
||||
|
||||
"""
|
||||
symbol = 'b'
|
||||
weights = {'n': BIGVAL, 'ne': BIGVAL, 'e': BIGVAL, 'se': BIGVAL,
|
||||
's': BIGVAL, 'sw': BIGVAL, 'w': BIGVAL, 'nw': BIGVAL}
|
||||
|
||||
symbol = "b"
|
||||
weights = {
|
||||
"n": BIGVAL,
|
||||
"ne": BIGVAL,
|
||||
"e": BIGVAL,
|
||||
"se": BIGVAL,
|
||||
"s": BIGVAL,
|
||||
"sw": BIGVAL,
|
||||
"w": BIGVAL,
|
||||
"nw": BIGVAL,
|
||||
}
|
||||
prototype = "xyz_exit"
|
||||
|
||||
|
||||
class RouterMapLink(SmartRerouterMapLink):
|
||||
"""A link that connects other links to build 'knees', pass-throughs etc."""
|
||||
|
||||
symbol = "o"
|
||||
|
||||
|
||||
|
|
@ -1266,7 +1335,8 @@ class TeleporterMapLink(SmartTeleporterMapLink):
|
|||
Teleporter links. Must appear in pairs on the same xy map. To make it one-way, add additional
|
||||
one-way link out of the teleporter on one side.
|
||||
"""
|
||||
symbol = 't'
|
||||
|
||||
symbol = "t"
|
||||
|
||||
|
||||
# all map components; used as base if not overridden
|
||||
|
|
@ -1291,5 +1361,5 @@ LEGEND = {
|
|||
"d": DownMapLink,
|
||||
"b": BlockedMapLink,
|
||||
"i": InterruptMapLink,
|
||||
't': TeleporterMapLink,
|
||||
"t": TeleporterMapLink,
|
||||
}
|
||||
|
|
|
|||
|
|
@ -28,6 +28,7 @@ class XYZGrid(DefaultScript):
|
|||
Main grid class. This organizes the Maps based on their name/Z-coordinate.
|
||||
|
||||
"""
|
||||
|
||||
def at_script_creation(self):
|
||||
"""
|
||||
What we store persistently is data used to create each map (the legends, names etc)
|
||||
|
|
@ -88,7 +89,7 @@ class XYZGrid(DefaultScript):
|
|||
"""
|
||||
return XYZRoom.objects.filter_xyz(xyz=xyz, **kwargs)
|
||||
|
||||
def get_exit(self, xyz, name='north', **kwargs):
|
||||
def get_exit(self, xyz, name="north", **kwargs):
|
||||
"""
|
||||
Get one or more exit object at coordinate.
|
||||
|
||||
|
|
@ -102,7 +103,7 @@ class XYZGrid(DefaultScript):
|
|||
Queryset: A queryset of XYZExit(s) found.
|
||||
|
||||
"""
|
||||
kwargs['db_key'] = name
|
||||
kwargs["db_key"] = name
|
||||
return XYZExit.objects.filter_xyz_exit(xyz=xyz, **kwargs)
|
||||
|
||||
def maps_from_module(self, module_path):
|
||||
|
|
@ -127,7 +128,7 @@ class XYZGrid(DefaultScript):
|
|||
if not mapdata:
|
||||
self.log(f"Could not find or load map from {module_path}.")
|
||||
return
|
||||
mapdata['module_path'] = module_path
|
||||
mapdata["module_path"] = module_path
|
||||
return map_data_list
|
||||
|
||||
def reload(self):
|
||||
|
|
@ -154,9 +155,9 @@ class XYZGrid(DefaultScript):
|
|||
# we reload the map from module
|
||||
new_mapdata = loaded_mapdata.get(zcoord)
|
||||
if not new_mapdata:
|
||||
if 'module_path' in old_mapdata:
|
||||
for mapdata in self.maps_from_module(old_mapdata['module_path']):
|
||||
loaded_mapdata[mapdata['zcoord']] = mapdata
|
||||
if "module_path" in old_mapdata:
|
||||
for mapdata in self.maps_from_module(old_mapdata["module_path"]):
|
||||
loaded_mapdata[mapdata["zcoord"]] = mapdata
|
||||
else:
|
||||
# nowhere to reload from - use what we have
|
||||
loaded_mapdata[zcoord] = old_mapdata
|
||||
|
|
@ -198,7 +199,7 @@ class XYZGrid(DefaultScript):
|
|||
|
||||
"""
|
||||
for mapdata in mapdatas:
|
||||
zcoord = mapdata.get('zcoord')
|
||||
zcoord = mapdata.get("zcoord")
|
||||
if not zcoord:
|
||||
raise RuntimeError("XYZGrid.add_map data must contain 'zcoord'.")
|
||||
|
||||
|
|
@ -220,7 +221,7 @@ class XYZGrid(DefaultScript):
|
|||
if remove_objects:
|
||||
# we can't batch-delete because we want to run the .delete
|
||||
# method that also wipes exits and moves content to save locations
|
||||
for xyzroom in XYZRoom.objects.filter_xyz(xyz=('*', '*', zcoord)):
|
||||
for xyzroom in XYZRoom.objects.filter_xyz(xyz=("*", "*", zcoord)):
|
||||
xyzroom.delete()
|
||||
self.reload()
|
||||
|
||||
|
|
@ -234,7 +235,7 @@ class XYZGrid(DefaultScript):
|
|||
self.remove_map(*(zcoord for zcoord in self.db.map_data), remove_objects=True)
|
||||
super().delete()
|
||||
|
||||
def spawn(self, xyz=('*', '*', '*'), directions=None):
|
||||
def spawn(self, xyz=("*", "*", "*"), directions=None):
|
||||
"""
|
||||
Create/recreate/update the in-game grid based on the stored Maps or for a specific Map
|
||||
or coordinate.
|
||||
|
|
@ -255,7 +256,7 @@ class XYZGrid(DefaultScript):
|
|||
|
||||
"""
|
||||
x, y, z = xyz
|
||||
wildcard = '*'
|
||||
wildcard = "*"
|
||||
|
||||
if z == wildcard:
|
||||
xymaps = self.grid
|
||||
|
|
@ -293,8 +294,10 @@ def get_xyzgrid(print_errors=True):
|
|||
xyzgrid.reload()
|
||||
return xyzgrid
|
||||
elif len(xyzgrid) > 1:
|
||||
("Warning: More than one XYZGrid instances were found. This is an error and "
|
||||
"only the first one will be used. Delete the other one(s) manually.")
|
||||
(
|
||||
"Warning: More than one XYZGrid instances were found. This is an error and "
|
||||
"only the first one will be used. Delete the other one(s) manually."
|
||||
)
|
||||
xyzgrid = xyzgrid[0]
|
||||
try:
|
||||
if not xyzgrid.ndb.loaded:
|
||||
|
|
|
|||
|
|
@ -31,7 +31,8 @@ class XYZManager(ObjectManager):
|
|||
efficiently querying the room in the database based on XY coordinates.
|
||||
|
||||
"""
|
||||
def filter_xyz(self, xyz=('*', '*', '*'), **kwargs):
|
||||
|
||||
def filter_xyz(self, xyz=("*", "*", "*"), **kwargs):
|
||||
"""
|
||||
Filter queryset based on XYZ position on the grid. The Z-position is the name of the XYMap
|
||||
Set a coordinate to `'*'` to act as a wildcard (setting all coords to `*` will thus find
|
||||
|
|
@ -49,23 +50,28 @@ class XYZManager(ObjectManager):
|
|||
|
||||
"""
|
||||
x, y, z = xyz
|
||||
wildcard = '*'
|
||||
wildcard = "*"
|
||||
|
||||
return (
|
||||
self
|
||||
.filter_family(**kwargs)
|
||||
self.filter_family(**kwargs)
|
||||
.filter(
|
||||
Q() if x == wildcard
|
||||
else Q(db_tags__db_key=str(x), db_tags__db_category=MAP_X_TAG_CATEGORY))
|
||||
Q()
|
||||
if x == wildcard
|
||||
else Q(db_tags__db_key=str(x), db_tags__db_category=MAP_X_TAG_CATEGORY)
|
||||
)
|
||||
.filter(
|
||||
Q() if y == wildcard
|
||||
else Q(db_tags__db_key=str(y), db_tags__db_category=MAP_Y_TAG_CATEGORY))
|
||||
Q()
|
||||
if y == wildcard
|
||||
else Q(db_tags__db_key=str(y), db_tags__db_category=MAP_Y_TAG_CATEGORY)
|
||||
)
|
||||
.filter(
|
||||
Q() if z == wildcard
|
||||
else Q(db_tags__db_key=str(z), db_tags__db_category=MAP_Z_TAG_CATEGORY))
|
||||
Q()
|
||||
if z == wildcard
|
||||
else Q(db_tags__db_key=str(z), db_tags__db_category=MAP_Z_TAG_CATEGORY)
|
||||
)
|
||||
)
|
||||
|
||||
def get_xyz(self, xyz=(0, 0, 'map'), **kwargs):
|
||||
def get_xyz(self, xyz=(0, 0, "map"), **kwargs):
|
||||
"""
|
||||
Always return a single matched entity directly. This accepts no `*`-wildcards.
|
||||
This will also find children of XYZRooms on the given coordinates.
|
||||
|
|
@ -93,8 +99,9 @@ class XYZManager(ObjectManager):
|
|||
|
||||
# error - mimic default get() behavior but with a little more info
|
||||
x, y, z = xyz
|
||||
inp = (f"Query: xyz=({x},{y},{z}), " +
|
||||
",".join(f"{key}={val}" for key, val in kwargs.items()))
|
||||
inp = f"Query: xyz=({x},{y},{z}), " + ",".join(
|
||||
f"{key}={val}" for key, val in kwargs.items()
|
||||
)
|
||||
if ncount > 1:
|
||||
raise self.model.MultipleObjectsReturned(inp)
|
||||
else:
|
||||
|
|
@ -108,8 +115,7 @@ class XYZExitManager(XYZManager):
|
|||
|
||||
"""
|
||||
|
||||
def filter_xyz_exit(self, xyz=('*', '*', '*'),
|
||||
xyz_destination=('*', '*', '*'), **kwargs):
|
||||
def filter_xyz_exit(self, xyz=("*", "*", "*"), xyz_destination=("*", "*", "*"), **kwargs):
|
||||
"""
|
||||
Used by exits (objects with a source and -destination property).
|
||||
Find all exits out of a source or to a particular destination. This will also find
|
||||
|
|
@ -138,32 +144,43 @@ class XYZExitManager(XYZManager):
|
|||
"""
|
||||
x, y, z = xyz
|
||||
xdest, ydest, zdest = xyz_destination
|
||||
wildcard = '*'
|
||||
wildcard = "*"
|
||||
|
||||
return (
|
||||
self
|
||||
.filter_family(**kwargs)
|
||||
self.filter_family(**kwargs)
|
||||
.filter(
|
||||
Q() if x == wildcard
|
||||
else Q(db_tags__db_key=str(x), db_tags__db_category=MAP_X_TAG_CATEGORY))
|
||||
Q()
|
||||
if x == wildcard
|
||||
else Q(db_tags__db_key=str(x), db_tags__db_category=MAP_X_TAG_CATEGORY)
|
||||
)
|
||||
.filter(
|
||||
Q() if y == wildcard
|
||||
else Q(db_tags__db_key=str(y), db_tags__db_category=MAP_Y_TAG_CATEGORY))
|
||||
Q()
|
||||
if y == wildcard
|
||||
else Q(db_tags__db_key=str(y), db_tags__db_category=MAP_Y_TAG_CATEGORY)
|
||||
)
|
||||
.filter(
|
||||
Q() if z == wildcard
|
||||
else Q(db_tags__db_key=str(z), db_tags__db_category=MAP_Z_TAG_CATEGORY))
|
||||
Q()
|
||||
if z == wildcard
|
||||
else Q(db_tags__db_key=str(z), db_tags__db_category=MAP_Z_TAG_CATEGORY)
|
||||
)
|
||||
.filter(
|
||||
Q() if xdest == wildcard
|
||||
else Q(db_tags__db_key=str(xdest), db_tags__db_category=MAP_XDEST_TAG_CATEGORY))
|
||||
Q()
|
||||
if xdest == wildcard
|
||||
else Q(db_tags__db_key=str(xdest), db_tags__db_category=MAP_XDEST_TAG_CATEGORY)
|
||||
)
|
||||
.filter(
|
||||
Q() if ydest == wildcard
|
||||
else Q(db_tags__db_key=str(ydest), db_tags__db_category=MAP_YDEST_TAG_CATEGORY))
|
||||
Q()
|
||||
if ydest == wildcard
|
||||
else Q(db_tags__db_key=str(ydest), db_tags__db_category=MAP_YDEST_TAG_CATEGORY)
|
||||
)
|
||||
.filter(
|
||||
Q() if zdest == wildcard
|
||||
else Q(db_tags__db_key=str(zdest), db_tags__db_category=MAP_ZDEST_TAG_CATEGORY))
|
||||
Q()
|
||||
if zdest == wildcard
|
||||
else Q(db_tags__db_key=str(zdest), db_tags__db_category=MAP_ZDEST_TAG_CATEGORY)
|
||||
)
|
||||
)
|
||||
|
||||
def get_xyz_exit(self, xyz=(0, 0, 'map'), xyz_destination=(0, 0, 'map'), **kwargs):
|
||||
def get_xyz_exit(self, xyz=(0, 0, "map"), xyz_destination=(0, 0, "map"), **kwargs):
|
||||
"""
|
||||
Used by exits (objects with a source and -destination property). Get a single
|
||||
exit. All source/destination coordinates (as well as the map's name) are required.
|
||||
|
|
@ -199,8 +216,7 @@ class XYZExitManager(XYZManager):
|
|||
|
||||
try:
|
||||
return (
|
||||
self
|
||||
.filter(db_tags__db_key=str(z), db_tags__db_category=MAP_Z_TAG_CATEGORY)
|
||||
self.filter(db_tags__db_key=str(z), db_tags__db_category=MAP_Z_TAG_CATEGORY)
|
||||
.filter(db_tags__db_key=str(x), db_tags__db_category=MAP_X_TAG_CATEGORY)
|
||||
.filter(db_tags__db_key=str(y), db_tags__db_category=MAP_Y_TAG_CATEGORY)
|
||||
.filter(db_tags__db_key=str(xdest), db_tags__db_category=MAP_XDEST_TAG_CATEGORY)
|
||||
|
|
@ -209,10 +225,12 @@ class XYZExitManager(XYZManager):
|
|||
.get(**kwargs)
|
||||
)
|
||||
except self.model.DoesNotExist:
|
||||
inp = (f"xyz=({x},{y},{z}),xyz_destination=({xdest},{ydest},{zdest})," +
|
||||
",".join(f"{key}={val}" for key, val in kwargs.items()))
|
||||
raise self.model.DoesNotExist(f"{self.model.__name__} "
|
||||
f"matching query {inp} does not exist.")
|
||||
inp = f"xyz=({x},{y},{z}),xyz_destination=({xdest},{ydest},{zdest})," + ",".join(
|
||||
f"{key}={val}" for key, val in kwargs.items()
|
||||
)
|
||||
raise self.model.DoesNotExist(
|
||||
f"{self.model.__name__} " f"matching query {inp} does not exist."
|
||||
)
|
||||
|
||||
|
||||
class XYZRoom(DefaultRoom):
|
||||
|
|
@ -244,10 +262,10 @@ class XYZRoom(DefaultRoom):
|
|||
|
||||
# default settings for map visualization
|
||||
map_display = True
|
||||
map_mode = 'nodes' # or 'scan'
|
||||
map_mode = "nodes" # or 'scan'
|
||||
map_visual_range = 2
|
||||
map_character_symbol = "|g@|n"
|
||||
map_align = 'c'
|
||||
map_align = "c"
|
||||
map_target_path_style = "|y{display_symbol}|n"
|
||||
map_fill_all = True
|
||||
map_separator_char = "|x~|n"
|
||||
|
|
@ -267,8 +285,10 @@ class XYZRoom(DefaultRoom):
|
|||
z = self.tags.get(category=MAP_Z_TAG_CATEGORY, return_list=False)
|
||||
if x is None or y is None or z is None:
|
||||
# don't cache unfinished coordinate (probably tags have not finished saving)
|
||||
return tuple(int(coord) if coord is not None and coord.isdigit() else coord
|
||||
for coord in (x, y, z))
|
||||
return tuple(
|
||||
int(coord) if coord is not None and coord.isdigit() else coord
|
||||
for coord in (x, y, z)
|
||||
)
|
||||
# cache result, convert to correct types (tags are strings)
|
||||
self._xyz = tuple(int(coord) if coord.isdigit() else coord for coord in (x, y, z))
|
||||
|
||||
|
|
@ -290,7 +310,7 @@ class XYZRoom(DefaultRoom):
|
|||
return self._xymap
|
||||
|
||||
@classmethod
|
||||
def create(cls, key, account=None, xyz=(0, 0, 'map'), **kwargs):
|
||||
def create(cls, key, account=None, xyz=(0, 0, "map"), **kwargs):
|
||||
"""
|
||||
Creation method aware of XYZ coordinates.
|
||||
|
||||
|
|
@ -316,14 +336,18 @@ class XYZRoom(DefaultRoom):
|
|||
try:
|
||||
x, y, z = xyz
|
||||
except ValueError:
|
||||
return None, [f"XYRroom.create got `xyz={xyz}` - needs a valid (X,Y,Z) "
|
||||
"coordinate of ints/strings."]
|
||||
return None, [
|
||||
f"XYRroom.create got `xyz={xyz}` - needs a valid (X,Y,Z) "
|
||||
"coordinate of ints/strings."
|
||||
]
|
||||
|
||||
existing_query = cls.objects.filter_xyz(xyz=(x, y, z))
|
||||
if existing_query.exists():
|
||||
existing_room = existing_query.first()
|
||||
return None, [f"XYRoom XYZ=({x},{y},{z}) already exists "
|
||||
f"(existing room is named '{existing_room.db_key}')!"]
|
||||
return None, [
|
||||
f"XYRoom XYZ=({x},{y},{z}) already exists "
|
||||
f"(existing room is named '{existing_room.db_key}')!"
|
||||
]
|
||||
|
||||
tags = (
|
||||
(str(x), MAP_X_TAG_CATEGORY),
|
||||
|
|
@ -410,26 +434,29 @@ class XYZRoom(DefaultRoom):
|
|||
xyz = self.xyz
|
||||
xymap = self.xyzgrid.get_map(xyz[2])
|
||||
|
||||
if xymap and kwargs.get('map_display', xymap.options.get("map_display", self.map_display)):
|
||||
if xymap and kwargs.get("map_display", xymap.options.get("map_display", self.map_display)):
|
||||
|
||||
# show the near-area map.
|
||||
map_character_symbol = kwargs.get(
|
||||
'map_character_symbol',
|
||||
xymap.options.get("map_character_symbol", self.map_character_symbol))
|
||||
"map_character_symbol",
|
||||
xymap.options.get("map_character_symbol", self.map_character_symbol),
|
||||
)
|
||||
map_visual_range = kwargs.get(
|
||||
"map_visual_range", xymap.options.get("map_visual_range", self.map_visual_range))
|
||||
map_mode = kwargs.get(
|
||||
"map_mode", xymap.options.get("map_mode", self.map_mode))
|
||||
map_align = kwargs.get(
|
||||
"map_align", xymap.options.get("map_align", self.map_align))
|
||||
"map_visual_range", xymap.options.get("map_visual_range", self.map_visual_range)
|
||||
)
|
||||
map_mode = kwargs.get("map_mode", xymap.options.get("map_mode", self.map_mode))
|
||||
map_align = kwargs.get("map_align", xymap.options.get("map_align", self.map_align))
|
||||
map_target_path_style = kwargs.get(
|
||||
"map_target_path_style",
|
||||
xymap.options.get("map_target_path_style", self.map_target_path_style))
|
||||
xymap.options.get("map_target_path_style", self.map_target_path_style),
|
||||
)
|
||||
map_area_client = kwargs.get(
|
||||
"map_fill_all", xymap.options.get("map_fill_all", self.map_fill_all))
|
||||
"map_fill_all", xymap.options.get("map_fill_all", self.map_fill_all)
|
||||
)
|
||||
map_separator_char = kwargs.get(
|
||||
"map_separator_char",
|
||||
xymap.options.get("map_separator_char", self.map_separator_char))
|
||||
xymap.options.get("map_separator_char", self.map_separator_char),
|
||||
)
|
||||
|
||||
client_width, _ = looker.sessions.get()[0].get_client_size()
|
||||
|
||||
|
|
@ -438,15 +465,14 @@ class XYZRoom(DefaultRoom):
|
|||
if map_area_client:
|
||||
display_width = client_width
|
||||
else:
|
||||
display_width = max(map_width,
|
||||
max(len(line) for line in room_desc.split("\n")))
|
||||
display_width = max(map_width, max(len(line) for line in room_desc.split("\n")))
|
||||
|
||||
# align map
|
||||
map_indent = 0
|
||||
sep_width = display_width
|
||||
if map_align == 'r':
|
||||
if map_align == "r":
|
||||
map_indent = max(0, display_width - map_width)
|
||||
elif map_align == 'c':
|
||||
elif map_align == "c":
|
||||
map_indent = max(0, (display_width - map_width) // 2)
|
||||
|
||||
# data set by the goto/path-command, for displaying the shortest path
|
||||
|
|
@ -462,7 +488,7 @@ class XYZRoom(DefaultRoom):
|
|||
target_path_style=map_target_path_style,
|
||||
character=map_character_symbol,
|
||||
max_size=(display_width, None),
|
||||
indent=map_indent
|
||||
indent=map_indent,
|
||||
)
|
||||
sep = map_separator_char * sep_width
|
||||
map_display = f"{sep}|n\n{map_display}\n{sep}"
|
||||
|
|
@ -523,8 +549,16 @@ class XYZExit(DefaultExit):
|
|||
return self._xyz_destination
|
||||
|
||||
@classmethod
|
||||
def create(cls, key, account=None, xyz=(0, 0, 'map'), xyz_destination=(0, 0, 'map'),
|
||||
location=None, destination=None, **kwargs):
|
||||
def create(
|
||||
cls,
|
||||
key,
|
||||
account=None,
|
||||
xyz=(0, 0, "map"),
|
||||
xyz_destination=(0, 0, "map"),
|
||||
location=None,
|
||||
destination=None,
|
||||
**kwargs,
|
||||
):
|
||||
"""
|
||||
Creation method aware of coordinates.
|
||||
|
||||
|
|
@ -559,23 +593,33 @@ class XYZExit(DefaultExit):
|
|||
return None, ["XYExit.create need either `xyz=(X,Y,Z)` coordinate or a `location`."]
|
||||
else:
|
||||
source = XYZRoom.objects.get_xyz(xyz=(x, y, z))
|
||||
tags.extend(((str(x), MAP_X_TAG_CATEGORY),
|
||||
(str(y), MAP_Y_TAG_CATEGORY),
|
||||
(str(z), MAP_Z_TAG_CATEGORY)))
|
||||
tags.extend(
|
||||
(
|
||||
(str(x), MAP_X_TAG_CATEGORY),
|
||||
(str(y), MAP_Y_TAG_CATEGORY),
|
||||
(str(z), MAP_Z_TAG_CATEGORY),
|
||||
)
|
||||
)
|
||||
if destination:
|
||||
dest = destination
|
||||
else:
|
||||
try:
|
||||
xdest, ydest, zdest = xyz_destination
|
||||
except ValueError:
|
||||
return None, ["XYExit.create need either `xyz_destination=(X,Y,Z)` coordinate "
|
||||
"or a `destination`."]
|
||||
return None, [
|
||||
"XYExit.create need either `xyz_destination=(X,Y,Z)` coordinate "
|
||||
"or a `destination`."
|
||||
]
|
||||
else:
|
||||
dest = XYZRoom.objects.get_xyz(xyz=(xdest, ydest, zdest))
|
||||
tags.extend(((str(xdest), MAP_XDEST_TAG_CATEGORY),
|
||||
(str(ydest), MAP_YDEST_TAG_CATEGORY),
|
||||
(str(zdest), MAP_ZDEST_TAG_CATEGORY)))
|
||||
tags.extend(
|
||||
(
|
||||
(str(xdest), MAP_XDEST_TAG_CATEGORY),
|
||||
(str(ydest), MAP_YDEST_TAG_CATEGORY),
|
||||
(str(zdest), MAP_ZDEST_TAG_CATEGORY),
|
||||
)
|
||||
)
|
||||
|
||||
return DefaultExit.create(
|
||||
key, source, dest,
|
||||
account=account, tags=tags, typeclass=cls, **kwargs)
|
||||
key, source, dest, account=account, tags=tags, typeclass=cls, **kwargs
|
||||
)
|
||||
|
|
|
|||
|
|
@ -6,4 +6,4 @@ Rolling dice - Griatch, 2012
|
|||
from .dice import roll # noqa
|
||||
from .dice import roll_dice # noqa
|
||||
from .dice import CmdDice # noqa
|
||||
from .dice import DiceCmdSet # noqa
|
||||
from .dice import DiceCmdSet # noqa
|
||||
|
|
|
|||
|
|
@ -58,8 +58,7 @@ from random import randint
|
|||
from evennia import default_cmds, CmdSet
|
||||
|
||||
|
||||
def roll(dicenum, dicetype, modifier=None,
|
||||
conditional=None, return_tuple=False):
|
||||
def roll(dicenum, dicetype, modifier=None, conditional=None, return_tuple=False):
|
||||
"""
|
||||
This is a standard dice roller.
|
||||
|
||||
|
|
@ -141,6 +140,7 @@ def roll(dicenum, dicetype, modifier=None,
|
|||
else:
|
||||
return result
|
||||
|
||||
|
||||
# legacy alias
|
||||
roll_dice = roll
|
||||
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ from .rpsystem import EmoteError, SdescError, RecogError, LanguageError # noqa
|
|||
from .rpsystem import ordered_permutation_regex, regex_tuple_from_key_alias # noqa
|
||||
from .rpsystem import parse_language, parse_sdescs_and_recogs, send_emote # noqa
|
||||
from .rpsystem import SdescHandler, RecogHandler # noqa
|
||||
from .rpsystem import RPCommand, CmdEmote, CmdSay, CmdSdesc, CmdPose, CmdRecog, CmdMask # noqa
|
||||
from .rpsystem import RPCommand, CmdEmote, CmdSay, CmdSdesc, CmdPose, CmdRecog, CmdMask # noqa
|
||||
from .rpsystem import RPSystemCmdSet # noqa
|
||||
from .rpsystem import ContribRPObject # noqa
|
||||
from .rpsystem import ContribRPRoom # noqa
|
||||
|
|
|
|||
|
|
@ -315,7 +315,8 @@ class LanguageHandler(DefaultScript):
|
|||
raise IndexError(
|
||||
"Could not find a matching phoneme for the grammar "
|
||||
f"'{match.group()}'. Make there is at least one phoneme matching this "
|
||||
"combination of consonants and vowels.")
|
||||
"combination of consonants and vowels."
|
||||
)
|
||||
translation[word.lower()] = new_word.lower()
|
||||
|
||||
if manual_translations:
|
||||
|
|
|
|||
|
|
@ -513,7 +513,7 @@ def parse_sdescs_and_recogs(sender, candidates, string, search_mode=False, case_
|
|||
errors.append(_EMOTE_NOMATCH_ERROR.format(ref=marker_match.group()))
|
||||
elif nmatches == 1:
|
||||
# a unique match - parse into intermediary representation
|
||||
case = '~' # retain original case of sdesc
|
||||
case = "~" # retain original case of sdesc
|
||||
if case_sensitive:
|
||||
# case sensitive mode
|
||||
# internal flags for the case used for the original /query
|
||||
|
|
@ -526,14 +526,14 @@ def parse_sdescs_and_recogs(sender, candidates, string, search_mode=False, case_
|
|||
# self-refs are kept as-is, others are parsed by case
|
||||
matchtext = marker_match.group().lstrip(_PREFIX)
|
||||
if matchtext.istitle():
|
||||
case = 't'
|
||||
case = "t"
|
||||
elif matchtext.isupper():
|
||||
case = '^'
|
||||
case = "^"
|
||||
elif matchtext.islower():
|
||||
case = 'v'
|
||||
case = "v"
|
||||
|
||||
key = "#%i%s" % (obj.id, case)
|
||||
string = string[:istart0] + "{%s}" % key + string[istart + maxscore:]
|
||||
string = string[:istart0] + "{%s}" % key + string[istart + maxscore :]
|
||||
mapping[key] = obj
|
||||
|
||||
else:
|
||||
|
|
@ -601,8 +601,9 @@ def send_emote(sender, receivers, emote, anonymous_add="first", **kwargs):
|
|||
"""
|
||||
case_sensitive = kwargs.pop("case_sensitive", True)
|
||||
try:
|
||||
emote, obj_mapping = parse_sdescs_and_recogs(sender, receivers, emote,
|
||||
case_sensitive=case_sensitive)
|
||||
emote, obj_mapping = parse_sdescs_and_recogs(
|
||||
sender, receivers, emote, case_sensitive=case_sensitive
|
||||
)
|
||||
emote, language_mapping = parse_language(sender, emote)
|
||||
except (EmoteError, LanguageError) as err:
|
||||
# handle all error messages, don't hide actual coding errors
|
||||
|
|
@ -615,8 +616,8 @@ def send_emote(sender, receivers, emote, anonymous_add="first", **kwargs):
|
|||
# (the text could have nested object mappings).
|
||||
emote = _RE_REF.sub(r"{{#\1}}", emote)
|
||||
# if anonymous_add is passed as a kwarg, collect and remove it from kwargs
|
||||
if 'anonymous_add' in kwargs:
|
||||
anonymous_add = kwargs.pop('anonymous_add')
|
||||
if "anonymous_add" in kwargs:
|
||||
anonymous_add = kwargs.pop("anonymous_add")
|
||||
if anonymous_add and not any(1 for tag in obj_mapping if tag.startswith(skey)):
|
||||
# no self-reference in the emote - add to the end
|
||||
obj_mapping[skey] = sender
|
||||
|
|
@ -670,12 +671,13 @@ def send_emote(sender, receivers, emote, anonymous_add="first", **kwargs):
|
|||
)
|
||||
# make sure receiver always sees their real name
|
||||
rkey_start = "#%i" % receiver.id
|
||||
rkey_keep_case = rkey_start + '~' # signifies keeping the case
|
||||
rkey_keep_case = rkey_start + "~" # signifies keeping the case
|
||||
for rkey in (key for key in receiver_sdesc_mapping if key.startswith(rkey_start)):
|
||||
# we could have #%i^, #%it etc depending on input case - we want the
|
||||
# self-reference to retain case.
|
||||
receiver_sdesc_mapping[rkey] = process_sdesc(
|
||||
receiver.key, receiver, ref=rkey_keep_case, **kwargs)
|
||||
receiver.key, receiver, ref=rkey_keep_case, **kwargs
|
||||
)
|
||||
|
||||
# do the template replacement of the sdesc/recog {#num} markers
|
||||
receiver.msg(sendemote.format(**receiver_sdesc_mapping), from_obj=sender, **kwargs)
|
||||
|
|
@ -1709,14 +1711,14 @@ class ContribRPCharacter(DefaultCharacter, ContribRPObject):
|
|||
if not sdesc:
|
||||
return ""
|
||||
|
||||
ref = kwargs.get('ref', '~') # ~ to keep sdesc unchanged
|
||||
if 't' in ref:
|
||||
ref = kwargs.get("ref", "~") # ~ to keep sdesc unchanged
|
||||
if "t" in ref:
|
||||
# we only want to capitalize the first letter if there are many words
|
||||
sdesc = sdesc.lower()
|
||||
sdesc = sdesc[0].upper() + sdesc[1:] if len(sdesc) > 1 else sdesc.upper()
|
||||
elif '^' in ref:
|
||||
elif "^" in ref:
|
||||
sdesc = sdesc.upper()
|
||||
elif 'v' in ref:
|
||||
elif "v" in ref:
|
||||
sdesc = sdesc.lower()
|
||||
return "|b%s|n" % sdesc
|
||||
|
||||
|
|
|
|||
|
|
@ -274,7 +274,7 @@ class TestRPSystem(BaseEvenniaTest):
|
|||
result = rpsystem.regex_tuple_from_key_alias(self.speaker)
|
||||
t2 = time.time()
|
||||
# print(f"t1: {t1 - t0}, t2: {t2 - t1}")
|
||||
self.assertLess(t2 - t1, 10 ** -4)
|
||||
self.assertLess(t2 - t1, 10**-4)
|
||||
self.assertEqual(result, (Anything, self.speaker, self.speaker.key))
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -59,16 +59,28 @@ class TraitHandlerTest(_TraitHandlerBase):
|
|||
super().setUp()
|
||||
self.traithandler.add("test1", name="Test1", trait_type="trait")
|
||||
self.traithandler.add(
|
||||
"test2", name="Test2", trait_type="trait", value=["foo", {"1": [1, 2, 3]}, 4],
|
||||
"test2",
|
||||
name="Test2",
|
||||
trait_type="trait",
|
||||
value=["foo", {"1": [1, 2, 3]}, 4],
|
||||
)
|
||||
|
||||
def test_add_trait(self):
|
||||
self.assertEqual(
|
||||
self._get_dbstore("test1"), {"name": "Test1", "trait_type": "trait", "value": None,}
|
||||
self._get_dbstore("test1"),
|
||||
{
|
||||
"name": "Test1",
|
||||
"trait_type": "trait",
|
||||
"value": None,
|
||||
},
|
||||
)
|
||||
self.assertEqual(
|
||||
self._get_dbstore("test2"),
|
||||
{"name": "Test2", "trait_type": "trait", "value": ["foo", {"1": [1, 2, 3]}, 4],},
|
||||
{
|
||||
"name": "Test2",
|
||||
"trait_type": "trait",
|
||||
"value": ["foo", {"1": [1, 2, 3]}, 4],
|
||||
},
|
||||
)
|
||||
self.assertEqual(len(self.traithandler), 2)
|
||||
|
||||
|
|
@ -328,7 +340,12 @@ class TestTraitCounter(_TraitHandlerBase):
|
|||
max=10,
|
||||
extra_val1="xvalue1",
|
||||
extra_val2="xvalue2",
|
||||
descs={0: "range0", 2: "range1", 5: "range2", 7: "range3",},
|
||||
descs={
|
||||
0: "range0",
|
||||
2: "range1",
|
||||
5: "range2",
|
||||
7: "range3",
|
||||
},
|
||||
)
|
||||
self.trait = self.traithandler.get("test1")
|
||||
|
||||
|
|
@ -348,7 +365,12 @@ class TestTraitCounter(_TraitHandlerBase):
|
|||
"max": 10,
|
||||
"extra_val1": "xvalue1",
|
||||
"extra_val2": "xvalue2",
|
||||
"descs": {0: "range0", 2: "range1", 5: "range2", 7: "range3",},
|
||||
"descs": {
|
||||
0: "range0",
|
||||
2: "range1",
|
||||
5: "range2",
|
||||
7: "range3",
|
||||
},
|
||||
"rate": 0,
|
||||
"ratetarget": None,
|
||||
"last_update": None,
|
||||
|
|
@ -507,7 +529,12 @@ class TestTraitCounterTimed(_TraitHandlerBase):
|
|||
max=100,
|
||||
extra_val1="xvalue1",
|
||||
extra_val2="xvalue2",
|
||||
descs={0: "range0", 2: "range1", 5: "range2", 7: "range3",},
|
||||
descs={
|
||||
0: "range0",
|
||||
2: "range1",
|
||||
5: "range2",
|
||||
7: "range3",
|
||||
},
|
||||
rate=1,
|
||||
ratetarget=None,
|
||||
)
|
||||
|
|
@ -579,7 +606,12 @@ class TestTraitGauge(_TraitHandlerBase):
|
|||
mod=2,
|
||||
extra_val1="xvalue1",
|
||||
extra_val2="xvalue2",
|
||||
descs={0: "range0", 2: "range1", 5: "range2", 7: "range3",},
|
||||
descs={
|
||||
0: "range0",
|
||||
2: "range1",
|
||||
5: "range2",
|
||||
7: "range3",
|
||||
},
|
||||
)
|
||||
self.trait = self.traithandler.get("test1")
|
||||
|
||||
|
|
@ -598,7 +630,12 @@ class TestTraitGauge(_TraitHandlerBase):
|
|||
"min": 0,
|
||||
"extra_val1": "xvalue1",
|
||||
"extra_val2": "xvalue2",
|
||||
"descs": {0: "range0", 2: "range1", 5: "range2", 7: "range3",},
|
||||
"descs": {
|
||||
0: "range0",
|
||||
2: "range1",
|
||||
5: "range2",
|
||||
7: "range3",
|
||||
},
|
||||
"rate": 0,
|
||||
"ratetarget": None,
|
||||
"last_update": None,
|
||||
|
|
@ -763,7 +800,12 @@ class TestTraitGaugeTimed(_TraitHandlerBase):
|
|||
min=0,
|
||||
extra_val1="xvalue1",
|
||||
extra_val2="xvalue2",
|
||||
descs={0: "range0", 2: "range1", 5: "range2", 7: "range3",},
|
||||
descs={
|
||||
0: "range0",
|
||||
2: "range1",
|
||||
5: "range2",
|
||||
7: "range3",
|
||||
},
|
||||
rate=1,
|
||||
ratetarget=None,
|
||||
)
|
||||
|
|
@ -831,8 +873,20 @@ class TestNumericTraitOperators(BaseEvenniaTestCase):
|
|||
|
||||
def setUp(self):
|
||||
# direct instantiation for testing only; use TraitHandler in production
|
||||
self.st = traits.Trait({"name": "Strength", "trait_type": "trait", "value": 8,})
|
||||
self.at = traits.Trait({"name": "Attack", "trait_type": "trait", "value": 4,})
|
||||
self.st = traits.Trait(
|
||||
{
|
||||
"name": "Strength",
|
||||
"trait_type": "trait",
|
||||
"value": 8,
|
||||
}
|
||||
)
|
||||
self.at = traits.Trait(
|
||||
{
|
||||
"name": "Attack",
|
||||
"trait_type": "trait",
|
||||
"value": 4,
|
||||
}
|
||||
)
|
||||
|
||||
def tearDown(self):
|
||||
self.st, self.at = None, None
|
||||
|
|
|
|||
|
|
@ -526,6 +526,7 @@ class MandatoryTraitKey:
|
|||
|
||||
"""
|
||||
|
||||
|
||||
class TraitHandler:
|
||||
"""
|
||||
Factory class that instantiates Trait objects. Must be assigned as a property
|
||||
|
|
@ -794,10 +795,7 @@ class TraitProperty:
|
|||
trait = traithandler.get(self._trait_key)
|
||||
if trait is None:
|
||||
# initialize the trait
|
||||
traithandler.add(
|
||||
self._trait_key,
|
||||
**self._trait_properties
|
||||
)
|
||||
traithandler.add(self._trait_key, **self._trait_properties)
|
||||
trait = traithandler.get(self._trait_key) # caches it in the traithandler
|
||||
self._cache[instance] = trait
|
||||
return self._cache[instance]
|
||||
|
|
@ -809,6 +807,7 @@ class TraitProperty:
|
|||
"""
|
||||
pass
|
||||
|
||||
|
||||
# Parent Trait class
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -42,6 +42,7 @@ from evennia.utils.utils import delay, repeat, interactive
|
|||
|
||||
# Commands for the state when the lid covering the button is closed.
|
||||
|
||||
|
||||
class CmdPushLidClosed(Command):
|
||||
"""
|
||||
Push the red button (lid closed)
|
||||
|
|
@ -121,23 +122,27 @@ class CmdSmashGlass(Command):
|
|||
"""
|
||||
rand = random.random()
|
||||
self.caller.location.msg_contents(
|
||||
f"{self.caller.name} tries to smash the glass of the button.",
|
||||
exclude=self.caller)
|
||||
f"{self.caller.name} tries to smash the glass of the button.", exclude=self.caller
|
||||
)
|
||||
|
||||
if rand < 0.2:
|
||||
string = ("You smash your hand against the glass"
|
||||
" with all your might. The lid won't budge"
|
||||
" but you cause quite the tremor through the button's mount."
|
||||
"\nIt looks like the button's lamp stopped working for the time being, "
|
||||
"but the lid is still as closed as ever.")
|
||||
string = (
|
||||
"You smash your hand against the glass"
|
||||
" with all your might. The lid won't budge"
|
||||
" but you cause quite the tremor through the button's mount."
|
||||
"\nIt looks like the button's lamp stopped working for the time being, "
|
||||
"but the lid is still as closed as ever."
|
||||
)
|
||||
# self.obj is the button itself
|
||||
self.obj.break_lamp()
|
||||
elif rand < 0.6:
|
||||
string = "You hit the lid hard. It doesn't move an inch."
|
||||
else:
|
||||
string = ("You place a well-aimed fist against the glass of the lid."
|
||||
" Unfortunately all you get is a pain in your hand. Maybe"
|
||||
" you should just try to just ... open the lid instead?")
|
||||
string = (
|
||||
"You place a well-aimed fist against the glass of the lid."
|
||||
" Unfortunately all you get is a pain in your hand. Maybe"
|
||||
" you should just try to just ... open the lid instead?"
|
||||
)
|
||||
self.caller.msg(string)
|
||||
|
||||
|
||||
|
|
@ -165,8 +170,8 @@ class CmdOpenLid(Command):
|
|||
string += "the lid will soon close again."
|
||||
self.caller.msg(string)
|
||||
self.caller.location.msg_contents(
|
||||
f"{self.caller.name} opens the lid of the button.",
|
||||
exclude=self.caller)
|
||||
f"{self.caller.name} opens the lid of the button.", exclude=self.caller
|
||||
)
|
||||
self.obj.to_open_state()
|
||||
|
||||
|
||||
|
|
@ -200,6 +205,7 @@ class LidClosedCmdSet(CmdSet):
|
|||
# Commands for the state when the button's protective cover is open - now the
|
||||
# push command will work. You can also close the lid again.
|
||||
|
||||
|
||||
class CmdPushLidOpen(Command):
|
||||
"""
|
||||
Push the red button
|
||||
|
|
@ -225,15 +231,15 @@ class CmdPushLidOpen(Command):
|
|||
"""
|
||||
# pause a little between each message.
|
||||
self.caller.msg("You reach out to press the big red button ...")
|
||||
yield(2) # pause 2s before next message
|
||||
yield (2) # pause 2s before next message
|
||||
self.caller.msg("\n\n|wBOOOOM! A bright light blinds you!|n")
|
||||
yield(1) # pause 1s before next message
|
||||
yield (1) # pause 1s before next message
|
||||
self.caller.msg("\n\n|xThe world goes dark ...|n")
|
||||
|
||||
name = self.caller.name
|
||||
self.caller.location.msg_contents(
|
||||
f"{name} presses the button. BOOM! {name} is blinded by a flash!",
|
||||
exclude=self.caller)
|
||||
f"{name} presses the button. BOOM! {name} is blinded by a flash!", exclude=self.caller
|
||||
)
|
||||
self.obj.blind_target(self.caller)
|
||||
|
||||
|
||||
|
|
@ -259,8 +265,8 @@ class CmdCloseLid(Command):
|
|||
# this will clean out scripts dependent on lid being open.
|
||||
self.caller.msg("You close the button's lid. It clicks back into place.")
|
||||
self.caller.location.msg_contents(
|
||||
f"{self.caller.name} closes the button's lid.",
|
||||
exclude=self.caller)
|
||||
f"{self.caller.name} closes the button's lid.", exclude=self.caller
|
||||
)
|
||||
|
||||
|
||||
class LidOpenCmdSet(CmdSet):
|
||||
|
|
@ -286,6 +292,7 @@ class LidOpenCmdSet(CmdSet):
|
|||
# Commands for when the button has been pushed and the player is blinded. This
|
||||
# replaces commands on the player making them 'blind' for a while.
|
||||
|
||||
|
||||
class CmdBlindLook(Command):
|
||||
"""
|
||||
Looking around in darkness
|
||||
|
|
@ -317,12 +324,14 @@ class CmdBlindLook(Command):
|
|||
string = "You fumble around, hands outstretched. You bump your knee."
|
||||
else:
|
||||
# trying to look
|
||||
string = ("You are temporarily blinded by the flash. "
|
||||
"Until it wears off, all you can do is feel around blindly.")
|
||||
string = (
|
||||
"You are temporarily blinded by the flash. "
|
||||
"Until it wears off, all you can do is feel around blindly."
|
||||
)
|
||||
self.caller.msg(string)
|
||||
self.caller.location.msg_contents(
|
||||
f"{self.caller.name} stumbles around, blinded.",
|
||||
exclude=self.caller)
|
||||
f"{self.caller.name} stumbles around, blinded.", exclude=self.caller
|
||||
)
|
||||
|
||||
|
||||
class CmdBlindHelp(Command):
|
||||
|
|
@ -420,20 +429,26 @@ class RedButton(DefaultObject):
|
|||
`button = create_object(RedButton, ..., attributes=[('desc', 'my desc')])`.
|
||||
|
||||
"""
|
||||
|
||||
# these are the pre-set descriptions. Setting attributes will override
|
||||
# these on the fly.
|
||||
|
||||
desc_closed_lid = ("This is a large red button, inviting yet evil-looking. "
|
||||
"A closed glass lid protects it.")
|
||||
desc_open_lid = ("This is a large red button, inviting yet evil-looking. "
|
||||
"Its glass cover is open and the button exposed.")
|
||||
desc_closed_lid = (
|
||||
"This is a large red button, inviting yet evil-looking. " "A closed glass lid protects it."
|
||||
)
|
||||
desc_open_lid = (
|
||||
"This is a large red button, inviting yet evil-looking. "
|
||||
"Its glass cover is open and the button exposed."
|
||||
)
|
||||
auto_close_msg = "The button's glass lid silently slides back in place."
|
||||
lamp_breaks_msg = "The lamp flickers, the button going dark."
|
||||
desc_add_lamp_broken = "\nThe big red button has stopped blinking for the time being."
|
||||
# note that this is a list. A random message will display each time
|
||||
blink_msgs = ["The red button flashes briefly.",
|
||||
"The red button blinks invitingly.",
|
||||
"The red button flashes. You know you wanna push it!"]
|
||||
blink_msgs = [
|
||||
"The red button flashes briefly.",
|
||||
"The red button blinks invitingly.",
|
||||
"The red button flashes. You know you wanna push it!",
|
||||
]
|
||||
|
||||
def at_object_creation(self):
|
||||
"""
|
||||
|
|
@ -523,9 +538,9 @@ class RedButton(DefaultObject):
|
|||
self.cmdset.add(LidOpenCmdSet)
|
||||
|
||||
# wait 20s then call self.to_closed_state with a message as argument
|
||||
delay(35, self.to_closed_state,
|
||||
self.db.auto_close_msg or self.auto_close_msg,
|
||||
persistent=True)
|
||||
delay(
|
||||
35, self.to_closed_state, self.db.auto_close_msg or self.auto_close_msg, persistent=True
|
||||
)
|
||||
|
||||
def _unblind_target(self, caller):
|
||||
"""
|
||||
|
|
@ -536,7 +551,8 @@ class RedButton(DefaultObject):
|
|||
caller.msg("You blink feverishly as your eyesight slowly returns.")
|
||||
self.location.msg_contents(
|
||||
f"{caller.name} seems to be recovering their eyesight, blinking feverishly.",
|
||||
exclude=caller)
|
||||
exclude=caller,
|
||||
)
|
||||
|
||||
def blind_target(self, caller):
|
||||
"""
|
||||
|
|
@ -554,8 +570,7 @@ class RedButton(DefaultObject):
|
|||
|
||||
# wait 20s then call self._unblind to remove blindness effect. The
|
||||
# persistent=True means the delay should survive a server reload.
|
||||
delay(20, self._unblind_target, caller,
|
||||
persistent=True)
|
||||
delay(20, self._unblind_target, caller, persistent=True)
|
||||
|
||||
def _unbreak_lamp(self):
|
||||
"""
|
||||
|
|
|
|||
|
|
@ -65,8 +65,10 @@ def info2(caller):
|
|||
|
||||
|
||||
def info3(caller):
|
||||
text = ("'Well ... I'm sort of busy so, have to go. NPC business. "
|
||||
"Important stuff. You wouldn't understand.'")
|
||||
text = (
|
||||
"'Well ... I'm sort of busy so, have to go. NPC business. "
|
||||
"Important stuff. You wouldn't understand.'"
|
||||
)
|
||||
|
||||
options = (
|
||||
{"desc": "Oookay ... I won't keep you. Bye.", "goto": "END"},
|
||||
|
|
@ -91,15 +93,15 @@ def END(caller):
|
|||
|
||||
class CmdTalk(default_cmds.MuxCommand):
|
||||
"""
|
||||
Talks to an npc
|
||||
Talks to an npc
|
||||
|
||||
Usage:
|
||||
talk
|
||||
Usage:
|
||||
talk
|
||||
|
||||
This command is only available if a talkative non-player-character
|
||||
(NPC) is actually present. It will strike up a conversation with
|
||||
that NPC and give you options on what to talk about.
|
||||
"""
|
||||
This command is only available if a talkative non-player-character
|
||||
(NPC) is actually present. It will strike up a conversation with
|
||||
that NPC and give you options on what to talk about.
|
||||
"""
|
||||
|
||||
key = "talk"
|
||||
locks = "cmd:all()"
|
||||
|
|
@ -113,8 +115,11 @@ class CmdTalk(default_cmds.MuxCommand):
|
|||
|
||||
# Initiate the menu. Change this if you are putting this on
|
||||
# some other custom NPC class.
|
||||
EvMenu(self.caller, "evennia.contrib.tutorials.talking_npc.talking_npc",
|
||||
startnode="menu_start_node")
|
||||
EvMenu(
|
||||
self.caller,
|
||||
"evennia.contrib.tutorials.talking_npc.talking_npc",
|
||||
startnode="menu_start_node",
|
||||
)
|
||||
|
||||
|
||||
class TalkingCmdSet(CmdSet):
|
||||
|
|
|
|||
|
|
@ -1158,7 +1158,8 @@ class TutorialWeaponRack(TutorialObject):
|
|||
|wstab/thrust/pierce <target>|n - poke at the enemy. More damage but harder to hit.
|
||||
|wslash/chop/bash <target>|n - swipe at the enemy. Less damage but easier to hit.
|
||||
|wdefend/parry|n - protect yourself and make yourself harder to hit.)
|
||||
""").strip()
|
||||
"""
|
||||
).strip()
|
||||
|
||||
self.db.no_more_weapons_msg = "you find nothing else of use."
|
||||
self.db.available_weapons = ["knife", "dagger", "sword", "club"]
|
||||
|
|
|
|||
|
|
@ -78,6 +78,7 @@ class CmdTutorial(Command):
|
|||
helptext += "\n\n (Write 'give up' if you want to abandon your quest.)"
|
||||
caller.msg(helptext)
|
||||
|
||||
|
||||
# for the @detail command we inherit from MuxCommand, since
|
||||
# we want to make use of MuxCommand's pre-parsing of '=' in the
|
||||
# argument.
|
||||
|
|
@ -202,22 +203,26 @@ class CmdTutorialLook(default_cmds.CmdLook):
|
|||
looking_at_obj.at_desc(looker=caller)
|
||||
return
|
||||
|
||||
|
||||
class CmdTutorialGiveUp(default_cmds.MuxCommand):
|
||||
"""
|
||||
Give up the tutorial-world quest and return to Limbo, the start room of the
|
||||
server.
|
||||
|
||||
"""
|
||||
|
||||
key = "give up"
|
||||
aliases = ['abort']
|
||||
aliases = ["abort"]
|
||||
|
||||
def func(self):
|
||||
outro_room = OutroRoom.objects.all()
|
||||
if outro_room:
|
||||
outro_room = outro_room[0]
|
||||
else:
|
||||
self.caller.msg("That didn't work (seems like a bug). "
|
||||
"Try to use the |wteleport|n command instead.")
|
||||
self.caller.msg(
|
||||
"That didn't work (seems like a bug). "
|
||||
"Try to use the |wteleport|n command instead."
|
||||
)
|
||||
return
|
||||
|
||||
self.caller.move_to(outro_room)
|
||||
|
|
@ -312,6 +317,7 @@ class TutorialStartExit(DefaultExit):
|
|||
will also clean up the intro command.
|
||||
|
||||
"""
|
||||
|
||||
def at_object_creation(self):
|
||||
self.cmdset.add(CmdSetEvenniaIntro, persistent=True)
|
||||
|
||||
|
|
@ -397,6 +403,7 @@ SUPERUSER_WARNING = (
|
|||
#
|
||||
# -------------------------------------------------------------
|
||||
|
||||
|
||||
class CmdEvenniaIntro(Command):
|
||||
"""
|
||||
Start the Evennia intro wizard.
|
||||
|
|
@ -405,10 +412,12 @@ class CmdEvenniaIntro(Command):
|
|||
intro
|
||||
|
||||
"""
|
||||
|
||||
key = "intro"
|
||||
|
||||
def func(self):
|
||||
from .intro_menu import init_menu
|
||||
|
||||
# quell also superusers
|
||||
if self.caller.account:
|
||||
self.caller.msg("Auto-quelling permissions while in intro ...")
|
||||
|
|
@ -463,6 +472,7 @@ class IntroRoom(TutorialRoom):
|
|||
character.account.execute_cmd("quell")
|
||||
character.msg("(Auto-quelling while in tutorial-world)")
|
||||
|
||||
|
||||
# -------------------------------------------------------------
|
||||
#
|
||||
# Bridge - unique room
|
||||
|
|
@ -1176,4 +1186,3 @@ class OutroRoom(TutorialRoom):
|
|||
def at_object_leave(self, character, destination):
|
||||
if character.account:
|
||||
character.account.execute_cmd("unquell")
|
||||
|
||||
|
|
|
|||
|
|
@ -12,10 +12,8 @@ from .server import AuditedServerSession
|
|||
from evennia.server.sessionhandler import SESSIONS
|
||||
|
||||
|
||||
@override_settings(
|
||||
AUDIT_MASKS=[])
|
||||
@override_settings(AUDIT_MASKS=[])
|
||||
class AuditingTest(BaseEvenniaTest):
|
||||
|
||||
@patch("evennia.server.sessionhandler._ServerSession", AuditedServerSession)
|
||||
def setup_session(self):
|
||||
"""Overrides default one in EvenniaTest"""
|
||||
|
|
@ -29,8 +27,10 @@ class AuditingTest(BaseEvenniaTest):
|
|||
SESSIONS.login(session, self.account, testmode=True)
|
||||
self.session = session
|
||||
|
||||
@patch("evennia.contrib.utils.auditing.server.AUDIT_CALLBACK",
|
||||
"evennia.contrib.utils.auditing.outputs.to_syslog")
|
||||
@patch(
|
||||
"evennia.contrib.utils.auditing.server.AUDIT_CALLBACK",
|
||||
"evennia.contrib.utils.auditing.outputs.to_syslog",
|
||||
)
|
||||
@patch("evennia.contrib.utils.auditing.server.AUDIT_IN", True)
|
||||
@patch("evennia.contrib.utils.auditing.server.AUDIT_OUT", True)
|
||||
def test_mask(self):
|
||||
|
|
@ -100,8 +100,10 @@ class AuditingTest(BaseEvenniaTest):
|
|||
for secret in secrets:
|
||||
self.assertEqual(self.session.mask(secret), secret)
|
||||
|
||||
@patch("evennia.contrib.utils.auditing.server.AUDIT_CALLBACK",
|
||||
"evennia.contrib.utils.auditing.outputs.to_syslog")
|
||||
@patch(
|
||||
"evennia.contrib.utils.auditing.server.AUDIT_CALLBACK",
|
||||
"evennia.contrib.utils.auditing.outputs.to_syslog",
|
||||
)
|
||||
@patch("evennia.contrib.utils.auditing.server.AUDIT_IN", True)
|
||||
@patch("evennia.contrib.utils.auditing.server.AUDIT_OUT", True)
|
||||
def test_audit(self):
|
||||
|
|
|
|||
|
|
@ -5,6 +5,6 @@ FieldFill contrib - Tim Ashley Jenkins 2018
|
|||
|
||||
from .fieldfill import FieldEvMenu # noqa
|
||||
from .fieldfill import CmdTestMenu # noqa
|
||||
from .fieldfill import init_fill_field # noqa
|
||||
from .fieldfill import form_template_to_dict # noqa
|
||||
from .fieldfill import display_formdata # noqa
|
||||
from .fieldfill import init_fill_field # noqa
|
||||
from .fieldfill import form_template_to_dict # noqa
|
||||
from .fieldfill import display_formdata # noqa
|
||||
|
|
|
|||
|
|
@ -5,4 +5,4 @@ Pseudo-random generator - vlgeoff 2017
|
|||
|
||||
from .random_string_generator import RandomStringGenerator # noqa
|
||||
from .random_string_generator import RandomStringGeneratorScript # noqa
|
||||
from .random_string_generator import RejectedRegex, ExhaustedGenerator # noqa
|
||||
from .random_string_generator import RejectedRegex, ExhaustedGenerator # noqa
|
||||
|
|
|
|||
|
|
@ -156,8 +156,10 @@ class RandomStringGenerator:
|
|||
self._find_elements(regex)
|
||||
|
||||
def __repr__(self):
|
||||
return "<evennia.contrib.utils.random_string_generator.RandomStringGenerator for {}>".format(
|
||||
self.name
|
||||
return (
|
||||
"<evennia.contrib.utils.random_string_generator.RandomStringGenerator for {}>".format(
|
||||
self.name
|
||||
)
|
||||
)
|
||||
|
||||
def _get_script(self):
|
||||
|
|
@ -169,7 +171,8 @@ class RandomStringGenerator:
|
|||
script = ScriptDB.objects.get(db_key="generator_script")
|
||||
except ScriptDB.DoesNotExist:
|
||||
script = create_script(
|
||||
"evennia.contrib.utils.random_string_generator.RandomStringGeneratorScript")
|
||||
"evennia.contrib.utils.random_string_generator.RandomStringGeneratorScript"
|
||||
)
|
||||
|
||||
type(self).script = script
|
||||
return script
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue