Run black reformatter on code

This commit is contained in:
Griatch 2022-02-08 13:03:52 +01:00
parent 4582eb4085
commit bd3e31bf3c
178 changed files with 4511 additions and 3385 deletions

View file

@ -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,
)
)

View file

@ -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()

View file

@ -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):

View file

@ -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

View file

@ -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):

View file

@ -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 = """

View file

@ -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)

View file

@ -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."

View file

@ -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(

View file

@ -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.
"""

View file

@ -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"

View file

@ -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}."
)

View file

@ -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"""

View file

@ -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)

View file

@ -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):

View file

@ -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

View file

@ -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)]},

View file

@ -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):

View file

@ -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

View file

@ -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

View file

@ -2,4 +2,3 @@
Contribs related to moving in and manipulating the game world and grid.
"""

View file

@ -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

View file

@ -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

View file

@ -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):

View file

@ -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]

View file

@ -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.")

View file

@ -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

View file

@ -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

View file

@ -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"):

View file

@ -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,
}

View file

@ -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:

View file

@ -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
)

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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:

View file

@ -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

View file

@ -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))

View file

@ -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

View file

@ -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

View file

@ -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):
"""

View file

@ -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):

View file

@ -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"]

View file

@ -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")

View file

@ -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):

View file

@ -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

View file

@ -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

View file

@ -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