mirror of
https://github.com/evennia/evennia.git
synced 2026-03-16 21:06:30 +01:00
Make numbered_names use get_display_name; make dbref display separate method
This commit is contained in:
parent
d893cfd46e
commit
cbe3d4c738
14 changed files with 242 additions and 122 deletions
16
CHANGELOG.md
16
CHANGELOG.md
|
|
@ -2,16 +2,30 @@
|
|||
|
||||
## Evennia Main branch
|
||||
|
||||
- Feature: *Backwards incompatible*: `DefaultObject.get_numbered_name` now gets object's
|
||||
name via `.get_display_name` for better compatibility with recog systems.
|
||||
- Feature: *Backwards incompatible*: Removed the (#dbref) display from
|
||||
`DefaultObject.get_display_name`, instead using new `.get_extra_display_name_info`
|
||||
method for getting this info. The Object's display template was extended for
|
||||
optionally adding this information. This makes showing extra object info to
|
||||
admins an explicit action and opens up `get_display_name` for general use.
|
||||
- Feature: Add `ON_DEMAND_HANDLER.set_dt(key, category, dt)` and
|
||||
`.set_stage(key, category, stage)` to allow manual tweaking of task timings,
|
||||
for example for a spell speeding a plant's growth (Griatch)
|
||||
- Feature: Add `use_assertequal` kwarg to the `EvenniaCommandTestMixin` testing
|
||||
class; this uses django's `assertEqual` over the default more lenient checker,
|
||||
which can be useful for testing table whitespace (Griatch)
|
||||
- Feature: New `utils.group_objects_by_key_and_desc` for grouping a list of
|
||||
objects based on the visible key and desc. Useful for inventory listings (Griatch)
|
||||
- Feature: Add `DefaultObject.get_numbered_name` `return_string` bool kwarg, for only
|
||||
returning singular/plural based on count instead of a tuple with both (Griatch)
|
||||
- Fix: `DefaultObject.get_numbered_name` used `.name` instead of
|
||||
`.get_display_name` which broke recog systems. May lead to object's #dbref
|
||||
will show for admins in some more places (Griatch)
|
||||
- [Fix][pull3420]: Refactor Clothing contrib's inventory command align with
|
||||
Evennia core's version (michaelfaith84, Griatch)
|
||||
- Fix: Resolve a bug when loading on-demand-handler data from database (Griatch)
|
||||
- Doc fixes (iLPdev, Griatch)
|
||||
- Doc fixes (iLPdev, Griatch, CloudKeeper)
|
||||
|
||||
[pull3420]: https://github.com/evennia/evennia/pull/3420
|
||||
|
||||
|
|
|
|||
|
|
@ -3,9 +3,8 @@ General Character commands usually available to all characters
|
|||
"""
|
||||
import re
|
||||
|
||||
from django.conf import settings
|
||||
|
||||
import evennia
|
||||
from django.conf import settings
|
||||
from evennia.typeclasses.attributes import NickTemplateInvalid
|
||||
from evennia.utils import utils
|
||||
|
||||
|
|
@ -370,11 +369,10 @@ class CmdInventory(COMMAND_DEFAULT_CLASS):
|
|||
from evennia.utils.ansi import raw as raw_ansi
|
||||
|
||||
table = self.styled_table(border="header")
|
||||
for item in items:
|
||||
singular, _ = item.get_numbered_name(1, self.caller)
|
||||
for key, desc, _ in utils.group_objects_by_key_and_desc(items, caller=self.caller):
|
||||
table.add_row(
|
||||
f"|C{singular}|n",
|
||||
"{}|n".format(utils.crop(raw_ansi(item.db.desc or ""), width=50) or ""),
|
||||
f"|C{key}|n",
|
||||
"{}|n".format(utils.crop(raw_ansi(desc or ""), width=50) or ""),
|
||||
)
|
||||
string = f"|wYou are carrying:\n{table}"
|
||||
self.msg(text=(string, {"type": "inventory"}))
|
||||
|
|
|
|||
|
|
@ -14,13 +14,10 @@ main test suite started with
|
|||
import datetime
|
||||
from unittest.mock import MagicMock, Mock, patch
|
||||
|
||||
import evennia
|
||||
from anything import Anything
|
||||
from django.conf import settings
|
||||
from django.test import override_settings
|
||||
from parameterized import parameterized
|
||||
from twisted.internet import task
|
||||
|
||||
import evennia
|
||||
from evennia import (
|
||||
DefaultCharacter,
|
||||
DefaultExit,
|
||||
|
|
@ -32,14 +29,7 @@ from evennia import (
|
|||
from evennia.commands import cmdparser
|
||||
from evennia.commands.cmdset import CmdSet
|
||||
from evennia.commands.command import Command, InterruptCommand
|
||||
from evennia.commands.default import (
|
||||
account,
|
||||
admin,
|
||||
batchprocess,
|
||||
building,
|
||||
comms,
|
||||
general,
|
||||
)
|
||||
from evennia.commands.default import account, admin, batchprocess, building, comms, general
|
||||
from evennia.commands.default import help as help_module
|
||||
from evennia.commands.default import syscommands, system, unloggedin
|
||||
from evennia.commands.default.cmdset_character import CharacterCmdSet
|
||||
|
|
@ -48,6 +38,8 @@ from evennia.prototypes import prototypes as protlib
|
|||
from evennia.utils import create, gametime, utils
|
||||
from evennia.utils.test_resources import BaseEvenniaCommandTest # noqa
|
||||
from evennia.utils.test_resources import BaseEvenniaTest, EvenniaCommandTest
|
||||
from parameterized import parameterized
|
||||
from twisted.internet import task
|
||||
|
||||
# ------------------------------------------------------------
|
||||
# Command testing
|
||||
|
|
@ -116,13 +108,13 @@ class TestGeneral(BaseEvenniaCommandTest):
|
|||
self.call(general.CmdNick(), "/list", "Defined Nicks:")
|
||||
|
||||
def test_get_and_drop(self):
|
||||
self.call(general.CmdGet(), "Obj", "You pick up an Obj.")
|
||||
self.call(general.CmdDrop(), "Obj", "You drop an Obj.")
|
||||
self.call(general.CmdGet(), "Obj", "You pick up an Obj")
|
||||
self.call(general.CmdDrop(), "Obj", "You drop an Obj")
|
||||
|
||||
def test_give(self):
|
||||
self.call(general.CmdGive(), "Obj to Char2", "You aren't carrying Obj.")
|
||||
self.call(general.CmdGive(), "Obj = Char2", "You aren't carrying Obj.")
|
||||
self.call(general.CmdGet(), "Obj", "You pick up an Obj.")
|
||||
self.call(general.CmdGet(), "Obj", "You pick up an Obj")
|
||||
self.call(general.CmdGive(), "Obj to Char2", "You give")
|
||||
self.call(general.CmdGive(), "Obj = Char", "You give", caller=self.char2)
|
||||
|
||||
|
|
@ -569,7 +561,7 @@ class TestAdmin(BaseEvenniaCommandTest):
|
|||
self.call(
|
||||
admin.CmdForce(),
|
||||
"Char2=say test",
|
||||
'Char2(#{}) says, "test"|You have forced Char2 to: say test'.format(cid),
|
||||
'Char2 says, "test"|You have forced Char2 to: say test',
|
||||
)
|
||||
|
||||
|
||||
|
|
@ -781,17 +773,14 @@ class TestBuilding(BaseEvenniaCommandTest):
|
|||
self.call(building.CmdExamine(), "*TestAccount")
|
||||
|
||||
def test_set_obj_alias(self):
|
||||
oid = self.obj1.id
|
||||
self.call(building.CmdSetObjAlias(), "Obj =", "Cleared aliases from Obj")
|
||||
self.call(
|
||||
building.CmdSetObjAlias(),
|
||||
"Obj = TestObj1b",
|
||||
"Alias(es) for 'Obj(#{})' set to 'testobj1b'.".format(oid),
|
||||
building.CmdSetObjAlias(), "Obj = TestObj1b", "Alias(es) for 'Obj' set to 'testobj1b'."
|
||||
)
|
||||
self.call(building.CmdSetObjAlias(), "", "Usage: ")
|
||||
self.call(building.CmdSetObjAlias(), "NotFound =", "Could not find 'NotFound'.")
|
||||
|
||||
self.call(building.CmdSetObjAlias(), "Obj", "Aliases for Obj(#{}): 'testobj1b'".format(oid))
|
||||
self.call(building.CmdSetObjAlias(), "Obj", "Aliases for Obj: 'testobj1b'")
|
||||
self.call(building.CmdSetObjAlias(), "Obj2 =", "Cleared aliases from Obj2")
|
||||
self.call(building.CmdSetObjAlias(), "Obj2 =", "No aliases to clear.")
|
||||
|
||||
|
|
@ -1228,9 +1217,7 @@ class TestBuilding(BaseEvenniaCommandTest):
|
|||
|
||||
def test_desc(self):
|
||||
oid = self.obj2.id
|
||||
self.call(
|
||||
building.CmdDesc(), "Obj2=TestDesc", "The description was set on Obj2(#{}).".format(oid)
|
||||
)
|
||||
self.call(building.CmdDesc(), "Obj2=TestDesc", "The description was set on Obj2.")
|
||||
self.call(building.CmdDesc(), "", "Usage: ")
|
||||
|
||||
with patch("evennia.commands.default.building.EvEditor") as mock_ed:
|
||||
|
|
@ -1251,7 +1238,7 @@ class TestBuilding(BaseEvenniaCommandTest):
|
|||
oid = self.obj2.id
|
||||
o2d = self.obj2.db.desc
|
||||
r1d = self.room1.db.desc
|
||||
self.call(building.CmdDesc(), "Obj2=", "The description was set on Obj2(#{}).".format(oid))
|
||||
self.call(building.CmdDesc(), "Obj2=", "The description was set on Obj2.")
|
||||
assert self.obj2.db.desc == "" and self.obj2.db.desc != o2d
|
||||
assert self.room1.db.desc == r1d
|
||||
|
||||
|
|
@ -1260,7 +1247,7 @@ class TestBuilding(BaseEvenniaCommandTest):
|
|||
rid = self.room1.id
|
||||
o2d = self.obj2.db.desc
|
||||
r1d = self.room1.db.desc
|
||||
self.call(building.CmdDesc(), "Obj2", "The description was set on Room(#{}).".format(rid))
|
||||
self.call(building.CmdDesc(), "Obj2", "The description was set on Room.")
|
||||
assert self.obj2.db.desc == o2d
|
||||
assert self.room1.db.desc == "Obj2" and self.room1.db.desc != r1d
|
||||
|
||||
|
|
@ -1283,16 +1270,11 @@ class TestBuilding(BaseEvenniaCommandTest):
|
|||
building.CmdDestroy(), settings.DEFAULT_HOME, "You are trying to delete"
|
||||
) # DEFAULT_HOME should not be deleted
|
||||
self.char2.location = self.room2
|
||||
charid = self.char2.id
|
||||
room1id = self.room1.id
|
||||
room2id = self.room2.id
|
||||
self.call(
|
||||
building.CmdDestroy(),
|
||||
self.room2.dbref,
|
||||
"Char2(#{}) arrives to Room(#{}) from Room2(#{}).|Room2 was destroyed.".format(
|
||||
charid, room1id, room2id
|
||||
),
|
||||
)
|
||||
"Char2 arrives to Room from Room2.|Room2 was destroyed.",
|
||||
),
|
||||
building.CmdDestroy.confirm = confirm
|
||||
|
||||
def test_destroy_sequence(self):
|
||||
|
|
@ -1640,9 +1622,6 @@ class TestBuilding(BaseEvenniaCommandTest):
|
|||
self.assertFalse(script3.pk)
|
||||
|
||||
def test_teleport(self):
|
||||
oid = self.obj1.id
|
||||
rid = self.room1.id
|
||||
rid2 = self.room2.id
|
||||
self.call(building.CmdTeleport(), "", "Usage: ")
|
||||
self.call(building.CmdTeleport(), "Obj = Room", "Obj is already at Room.")
|
||||
self.call(
|
||||
|
|
@ -1653,9 +1632,7 @@ class TestBuilding(BaseEvenniaCommandTest):
|
|||
self.call(
|
||||
building.CmdTeleport(),
|
||||
"Obj = Room2",
|
||||
"Obj(#{}) is leaving Room(#{}), heading for Room2(#{}).|Teleported Obj -> Room2.".format(
|
||||
oid, rid, rid2
|
||||
),
|
||||
"Obj is leaving Room, heading for Room2.|Teleported Obj -> Room2.",
|
||||
)
|
||||
self.call(building.CmdTeleport(), "NotFound = Room", "Could not find 'NotFound'.")
|
||||
self.call(
|
||||
|
|
@ -1663,7 +1640,7 @@ class TestBuilding(BaseEvenniaCommandTest):
|
|||
)
|
||||
|
||||
self.call(building.CmdTeleport(), "/tonone Obj2", "Teleported Obj2 -> None-location.")
|
||||
self.call(building.CmdTeleport(), "/quiet Room2", "Room2(#{})".format(rid2))
|
||||
self.call(building.CmdTeleport(), "/quiet Room2", "Room2")
|
||||
self.call(
|
||||
building.CmdTeleport(),
|
||||
"/t", # /t switch is abbreviated form of /tonone
|
||||
|
|
@ -1777,7 +1754,8 @@ class TestBuilding(BaseEvenniaCommandTest):
|
|||
self.call(
|
||||
building.CmdSpawn(),
|
||||
"{'prototype_key':'GOBLIN', 'typeclass':'evennia.objects.objects.DefaultCharacter', "
|
||||
"'key':'goblin', 'location':'%s'}" % spawnLoc.dbref,
|
||||
"'key':'goblin', 'location':'%s'}"
|
||||
% spawnLoc.dbref,
|
||||
"Spawned goblin",
|
||||
)
|
||||
goblin = get_object(self, "goblin")
|
||||
|
|
@ -1825,7 +1803,8 @@ class TestBuilding(BaseEvenniaCommandTest):
|
|||
self.call(
|
||||
building.CmdSpawn(),
|
||||
"/noloc {'prototype_parent':'TESTBALL', 'key': 'Ball', 'prototype_key': 'foo',"
|
||||
" 'location':'%s'}" % spawnLoc.dbref,
|
||||
" 'location':'%s'}"
|
||||
% spawnLoc.dbref,
|
||||
"Spawned Ball",
|
||||
)
|
||||
ball = get_object(self, "Ball")
|
||||
|
|
|
|||
|
|
@ -183,7 +183,7 @@ class TestEvscaperoomCommands(BaseEvenniaCommandTest):
|
|||
self.call(
|
||||
commands.CmdEmote(),
|
||||
"/me smiles to /obj",
|
||||
f"Char(#{self.char1.id}) smiles to Obj(#{self.obj1.id})",
|
||||
f"Char smiles to Obj.",
|
||||
)
|
||||
|
||||
def test_focus_interaction(self):
|
||||
|
|
|
|||
|
|
@ -77,7 +77,15 @@ from collections import defaultdict
|
|||
from django.conf import settings
|
||||
from evennia import DefaultCharacter, DefaultObject, default_cmds
|
||||
from evennia.commands.default.muxcommand import MuxCommand
|
||||
from evennia.utils import at_search_result, crop, evtable, inherits_from, int2str, iter_to_str
|
||||
from evennia.utils import (
|
||||
at_search_result,
|
||||
crop,
|
||||
evtable,
|
||||
group_objects_by_key_and_desc,
|
||||
inherits_from,
|
||||
int2str,
|
||||
iter_to_str,
|
||||
)
|
||||
from evennia.utils.ansi import raw as raw_ansi
|
||||
|
||||
# Options start here.
|
||||
|
|
@ -660,11 +668,10 @@ class CmdInventory(MuxCommand):
|
|||
carried = [obj for obj in items if not obj.db.worn]
|
||||
carry_table = self.styled_table(border="header")
|
||||
|
||||
for item in carried:
|
||||
singular, _ = item.get_numbered_name(1, self.caller)
|
||||
for key, desc, _ in group_objects_by_key_and_desc(carried, caller=self.caller):
|
||||
carry_table.add_row(
|
||||
f"{singular}|n",
|
||||
"{}|n".format(crop(raw_ansi(item.db.desc or ""), width=50) or ""),
|
||||
f"{key}|n",
|
||||
"{}|n".format(crop(raw_ansi(desc or ""), width=50) or ""),
|
||||
)
|
||||
message_list.extend(
|
||||
["|wYou are carrying:|n", str(carry_table) if carry_table.nrows > 0 else " Nothing."]
|
||||
|
|
@ -674,18 +681,17 @@ class CmdInventory(MuxCommand):
|
|||
worn = [obj for obj in items if obj.db.worn]
|
||||
wear_table = self.styled_table(border="header")
|
||||
|
||||
for item in worn:
|
||||
singular, _ = item.get_numbered_name(1, self.caller)
|
||||
for key, desc, _ in group_objects_by_key_and_desc(worn, caller=self.caller):
|
||||
wear_table.add_row(
|
||||
f"{singular}|n",
|
||||
"{}|n".format(crop(raw_ansi(item.db.desc or ""), width=50) or ""),
|
||||
f"{key}|n",
|
||||
"{}|n".format(crop(raw_ansi(desc or ""), width=50) or ""),
|
||||
)
|
||||
message_list.extend(
|
||||
["You are wearing:|n", str(wear_table) if wear_table.nrows > 0 else " Nothing."]
|
||||
)
|
||||
|
||||
# return the composite message
|
||||
self.caller.msg("\n".join(message_list))
|
||||
self.caller.msg(text=("\n".join(message_list), {"type": "inventory"}))
|
||||
|
||||
|
||||
class ClothedCharacterCmdSet(default_cmds.CharacterCmdSet):
|
||||
|
|
|
|||
|
|
@ -19,9 +19,11 @@ class TestClothingCmd(BaseEvenniaCommandTest):
|
|||
self.wearer.location = self.room
|
||||
# Make a test hat
|
||||
self.test_hat = create_object(clothing.ContribClothing, key="test hat")
|
||||
self.test_hat.db.desc = "A test hat."
|
||||
self.test_hat.db.clothing_type = "hat"
|
||||
# Make a test scarf
|
||||
self.test_scarf = create_object(clothing.ContribClothing, key="test scarf")
|
||||
self.test_scarf.db.desc = "A test scarf."
|
||||
self.test_scarf.db.clothing_type = "accessory"
|
||||
|
||||
def test_clothingcommands(self):
|
||||
|
|
@ -40,7 +42,10 @@ class TestClothingCmd(BaseEvenniaCommandTest):
|
|||
self.call(
|
||||
clothing.CmdInventory(),
|
||||
"",
|
||||
"You are carrying:\n a test scarf \n a test hat \nYou are wearing:\n Nothing.",
|
||||
(
|
||||
"You are carrying:\n a test hat A test hat. \n a test scarf A test"
|
||||
" scarf. \nYou are wearing:\n Nothing."
|
||||
),
|
||||
caller=self.wearer,
|
||||
use_assertequal=True,
|
||||
)
|
||||
|
|
@ -71,7 +76,10 @@ class TestClothingCmd(BaseEvenniaCommandTest):
|
|||
self.call(
|
||||
clothing.CmdInventory(),
|
||||
"",
|
||||
"You are carrying:\n Nothing.\nYou are wearing:\n a test scarf \n a test hat ",
|
||||
(
|
||||
"You are carrying:\n Nothing.\nYou are wearing:\n a test hat A test hat. \n"
|
||||
" a test scarf A test scarf. "
|
||||
),
|
||||
caller=self.wearer,
|
||||
use_assertequal=True,
|
||||
)
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
from evennia import create_object
|
||||
from evennia.utils.test_resources import BaseEvenniaCommandTest, BaseEvenniaTest # noqa
|
||||
from evennia.utils.test_resources import BaseEvenniaCommandTest # noqa
|
||||
from evennia.utils.test_resources import BaseEvenniaTest
|
||||
|
||||
from .containers import CmdContainerGet, CmdContainerLook, CmdPut, ContribContainer
|
||||
|
||||
|
|
@ -40,9 +41,17 @@ class TestContainerCmds(BaseEvenniaCommandTest):
|
|||
# get normally
|
||||
self.call(CmdContainerGet(), "Obj", "You pick up an Obj.")
|
||||
# put in the container
|
||||
self.call(CmdPut(), "obj in box", "You put an Obj in a Box.")
|
||||
self.call(
|
||||
CmdPut(),
|
||||
"obj in box",
|
||||
"You put an Obj in a Box.",
|
||||
)
|
||||
# get from the container
|
||||
self.call(CmdContainerGet(), "obj from box", "You get an Obj from a Box.")
|
||||
self.call(
|
||||
CmdContainerGet(),
|
||||
"obj from box",
|
||||
"You get an Obj from a Box.",
|
||||
)
|
||||
|
||||
def test_locked_get_put(self):
|
||||
# lock container
|
||||
|
|
|
|||
|
|
@ -6,11 +6,10 @@ Testing of ExtendedRoom contrib
|
|||
import datetime
|
||||
|
||||
from django.conf import settings
|
||||
from mock import Mock, patch
|
||||
from parameterized import parameterized
|
||||
|
||||
from evennia import create_object
|
||||
from evennia.utils.test_resources import BaseEvenniaCommandTest, EvenniaTestCase
|
||||
from mock import Mock, patch
|
||||
from parameterized import parameterized
|
||||
|
||||
from . import extended_room
|
||||
|
||||
|
|
@ -195,7 +194,7 @@ class TestExtendedRoomCommands(BaseEvenniaCommandTest):
|
|||
extended_room.CmdExtendedRoomDesc(),
|
||||
"",
|
||||
f"""
|
||||
Room Room(#{self.room1.id}) Season: autumn. Time: afternoon. States: None
|
||||
Room Room Season: autumn. Time: afternoon. States: None
|
||||
|
||||
Room state (default) (active):
|
||||
Base room description.
|
||||
|
|
@ -218,7 +217,7 @@ Base room description.
|
|||
extended_room.CmdExtendedRoomDesc(),
|
||||
"",
|
||||
f"""
|
||||
Room Room(#{self.room1.id}) Season: autumn. Time: afternoon. States: None
|
||||
Room Room Season: autumn. Time: afternoon. States: None
|
||||
|
||||
Room state burning:
|
||||
Burning description.
|
||||
|
|
@ -235,8 +234,10 @@ Base room description.
|
|||
self.call(
|
||||
extended_room.CmdExtendedRoomDesc(),
|
||||
"/del/burning/spring",
|
||||
"The burning-description was deleted, if it existed.|The spring-description was"
|
||||
" deleted, if it existed",
|
||||
(
|
||||
"The burning-description was deleted, if it existed.|The spring-description was"
|
||||
" deleted, if it existed"
|
||||
),
|
||||
)
|
||||
# add autumn, which should be active
|
||||
self.call(
|
||||
|
|
@ -248,7 +249,7 @@ Base room description.
|
|||
extended_room.CmdExtendedRoomDesc(),
|
||||
"",
|
||||
f"""
|
||||
Room Room(#{self.room1.id}) Season: autumn. Time: afternoon. States: None
|
||||
Room Room Season: autumn. Time: afternoon. States: None
|
||||
|
||||
Room state autumn (active):
|
||||
Autumn description.
|
||||
|
|
@ -285,8 +286,8 @@ test: Test detail.
|
|||
self.call(
|
||||
extended_room.CmdExtendedRoomDetail(),
|
||||
"",
|
||||
f"""
|
||||
The room Room(#{self.room1.id}) doesn't have any details.
|
||||
"""
|
||||
The room Room doesn't have any details.
|
||||
""".strip(),
|
||||
)
|
||||
|
||||
|
|
@ -306,7 +307,7 @@ The room Room(#{self.room1.id}) doesn't have any details.
|
|||
self.call(
|
||||
extended_room.CmdExtendedRoomState(),
|
||||
"",
|
||||
f"Room states (not counting automatic time/season) on Room(#{self.room1.id}):\n None",
|
||||
"Room states (not counting automatic time/season) on Room:\n None",
|
||||
)
|
||||
|
||||
# add room states
|
||||
|
|
@ -323,8 +324,7 @@ The room Room(#{self.room1.id}) doesn't have any details.
|
|||
self.call(
|
||||
extended_room.CmdExtendedRoomState(),
|
||||
"",
|
||||
f"Room states (not counting automatic time/season) on Room(#{self.room1.id}):\n "
|
||||
"'burning' and 'windy'",
|
||||
f"Room states (not counting automatic time/season) on Room:\n 'burning' and 'windy'",
|
||||
)
|
||||
# toggle windy
|
||||
self.call(
|
||||
|
|
@ -335,8 +335,7 @@ The room Room(#{self.room1.id}) doesn't have any details.
|
|||
self.call(
|
||||
extended_room.CmdExtendedRoomState(),
|
||||
"",
|
||||
f"Room states (not counting automatic time/season) on Room(#{self.room1.id}):\n "
|
||||
"'burning'",
|
||||
f"Room states (not counting automatic time/season) on Room:\n 'burning'",
|
||||
)
|
||||
# add a autumn state and make sure we override it
|
||||
self.room1.add_desc("Autumn description.", room_state="autumn")
|
||||
|
|
@ -387,13 +386,17 @@ The room Room(#{self.room1.id}) doesn't have any details.
|
|||
self.call(
|
||||
extended_room.CmdExtendedRoomLook(),
|
||||
"",
|
||||
f"Room(#{self.room1.id})\nThis is a nice autumnal forest. The afternoon sun is"
|
||||
" shining through the trees.",
|
||||
(
|
||||
f"Room(#{self.room1.id})\nThis is a nice autumnal forest. The afternoon sun is"
|
||||
" shining through the trees."
|
||||
),
|
||||
)
|
||||
self.room1.add_room_state("burning")
|
||||
self.call(
|
||||
extended_room.CmdExtendedRoomLook(),
|
||||
"",
|
||||
f"Room(#{self.room1.id})\nThis is a nice autumnal forest. The afternoon sun is"
|
||||
" shining through the trees and this place is on fire!",
|
||||
(
|
||||
f"Room(#{self.room1.id})\nThis is a nice autumnal forest. The afternoon sun is"
|
||||
" shining through the trees and this place is on fire!"
|
||||
),
|
||||
)
|
||||
|
|
|
|||
|
|
@ -154,18 +154,12 @@ from string import punctuation
|
|||
|
||||
import inflect
|
||||
from django.conf import settings
|
||||
|
||||
from evennia.commands.cmdset import CmdSet
|
||||
from evennia.commands.command import Command
|
||||
from evennia.objects.models import ObjectDB
|
||||
from evennia.objects.objects import DefaultCharacter, DefaultObject
|
||||
from evennia.utils import ansi, logger
|
||||
from evennia.utils.utils import (
|
||||
iter_to_str,
|
||||
lazy_property,
|
||||
make_iter,
|
||||
variable_from_module,
|
||||
)
|
||||
from evennia.utils.utils import iter_to_str, lazy_property, make_iter, variable_from_module
|
||||
|
||||
_INFLECT = inflect.engine()
|
||||
|
||||
|
|
@ -1343,13 +1337,15 @@ class ContribRPObject(DefaultObject):
|
|||
# in eventual error reporting later (not their keys). Doing
|
||||
# it like this e.g. allows for use of the typeclass kwarg
|
||||
# limiter.
|
||||
results.extend([obj for obj in search_obj(candidate.key) if obj not in results])
|
||||
results.extend(
|
||||
[obj for obj in search_obj(candidate.key, **kwargs) if obj not in results]
|
||||
)
|
||||
|
||||
if not results and is_builder:
|
||||
# builders get a chance to search only by key+alias
|
||||
results = search_obj(searchdata, candidates=candidates, **kwargs)
|
||||
# builders get to do a global search by key+alias
|
||||
results = search_obj(searchdata, **kwargs)
|
||||
else:
|
||||
# global searches / #drefs end up here. Global searches are
|
||||
# global searches with #drefs end up here. Global searches are
|
||||
# only done in code, so is controlled, #dbrefs are turned off
|
||||
# for non-Builders.
|
||||
results = search_obj(searchdata, **kwargs)
|
||||
|
|
@ -1409,10 +1405,6 @@ class ContribRPObject(DefaultObject):
|
|||
# use own sdesc as a fallback
|
||||
sdesc = self.sdesc.get()
|
||||
|
||||
# add dbref is looker has control access and `noid` is not set
|
||||
if self.access(looker, access_type="control") and not kwargs.get("noid", False):
|
||||
sdesc = f"{sdesc}(#{self.id})"
|
||||
|
||||
return self.get_posed_sdesc(sdesc) if kwargs.get("pose", False) else sdesc
|
||||
|
||||
def get_display_characters(self, looker, pose=True, **kwargs):
|
||||
|
|
@ -1545,10 +1537,6 @@ class ContribRPCharacter(DefaultCharacter, ContribRPObject):
|
|||
# use own sdesc as a fallback
|
||||
sdesc = self.sdesc.get()
|
||||
|
||||
# add dbref is looker has control access and `noid` is not set
|
||||
if self.access(looker, access_type="control") and not kwargs.get("noid", False):
|
||||
sdesc = f"{sdesc}(#{self.id})"
|
||||
|
||||
return self.get_posed_sdesc(sdesc) if kwargs.get("pose", False) else sdesc
|
||||
|
||||
def at_object_creation(self):
|
||||
|
|
|
|||
|
|
@ -5,7 +5,6 @@ Tests for RP system
|
|||
import time
|
||||
|
||||
from anything import Anything
|
||||
|
||||
from evennia import DefaultObject, create_object, default_cmds
|
||||
from evennia.commands.default.tests import BaseEvenniaCommandTest
|
||||
from evennia.utils.test_resources import BaseEvenniaTest
|
||||
|
|
@ -414,14 +413,14 @@ class TestRPSystemCommands(BaseEvenniaCommandTest):
|
|||
|
||||
expected_first_call = [
|
||||
"More than one match for 'Mushroom' (please narrow target):",
|
||||
f" Mushroom({mushroom1.dbref})-1 []",
|
||||
f" Mushroom({mushroom2.dbref})-2 []",
|
||||
f" Mushroom-1 []",
|
||||
f" Mushroom-2 []",
|
||||
]
|
||||
|
||||
self.call(default_cmds.CmdLook(), "Mushroom", "\n".join(expected_first_call)) # PASSES
|
||||
|
||||
expected_second_call = f"Mushroom({mushroom1.dbref})\nThe first mushroom is brown."
|
||||
expected_second_call = f"Mushroom(#{mushroom1.id})\nThe first mushroom is brown."
|
||||
self.call(default_cmds.CmdLook(), "Mushroom-1", expected_second_call) # FAILS
|
||||
|
||||
expected_third_call = f"Mushroom({mushroom2.dbref})\nThe second mushroom is red."
|
||||
expected_third_call = f"Mushroom(#{mushroom2.id})\nThe second mushroom is red."
|
||||
self.call(default_cmds.CmdLook(), "Mushroom-2", expected_third_call) # FAILS
|
||||
|
|
|
|||
|
|
@ -19,7 +19,6 @@ from django.conf import settings
|
|||
from django.core.exceptions import ObjectDoesNotExist
|
||||
from django.core.validators import validate_comma_separated_integer_list
|
||||
from django.db import models
|
||||
|
||||
from evennia.objects.manager import ObjectDBManager
|
||||
from evennia.typeclasses.models import TypedObject
|
||||
from evennia.utils import logger
|
||||
|
|
|
|||
|
|
@ -219,7 +219,7 @@ class DefaultObject(ObjectDB, metaclass=TypeclassBase):
|
|||
# populated by `return_appearance`
|
||||
appearance_template = """
|
||||
{header}
|
||||
|c{name}|n
|
||||
|c{name}{extra_name_info}|n
|
||||
{desc}
|
||||
{exits}{characters}{things}
|
||||
{footer}
|
||||
|
|
@ -316,7 +316,7 @@ class DefaultObject(ObjectDB, metaclass=TypeclassBase):
|
|||
"obj.location to move an object here.".format(self.__class__)
|
||||
)
|
||||
|
||||
contents = property(contents_get, contents_set, contents_set)
|
||||
contents = property(contents_get, contents_set, contents_set, contents_set)
|
||||
|
||||
@property
|
||||
def exits(self):
|
||||
|
|
@ -827,6 +827,16 @@ class DefaultObject(ObjectDB, metaclass=TypeclassBase):
|
|||
for session in sessions:
|
||||
session.data_out(**kwargs)
|
||||
|
||||
def get_contents_unique(self, caller=None):
|
||||
"""
|
||||
Get a mapping of contents that are visually unique to the caller, along with
|
||||
how many of each there are.
|
||||
|
||||
Args:
|
||||
caller (Object, optional): The object to check visibility from. If not given,
|
||||
the current object will be used.
|
||||
"""
|
||||
|
||||
def for_contents(self, func, exclude=None, **kwargs):
|
||||
"""
|
||||
Runs a function on every object contained within this one.
|
||||
|
|
@ -1436,10 +1446,28 @@ class DefaultObject(ObjectDB, metaclass=TypeclassBase):
|
|||
and is expected to produce something useful for builders.
|
||||
|
||||
"""
|
||||
if looker and self.locks.check_lockstring(looker, "perm(Builder)"):
|
||||
return "{}(#{})".format(self.name, self.id)
|
||||
return self.name
|
||||
|
||||
def get_extra_display_name_info(self, looker=None, **kwargs):
|
||||
"""
|
||||
Adds any extra display information to the object's name. By default this is is the
|
||||
object's dbref in parentheses, if the looker has permission to see it.
|
||||
|
||||
Args:
|
||||
looker (Object): The object looking at this object.
|
||||
|
||||
Returns:
|
||||
str: The dbref of this object, if the looker has permission to see it. Otherwise, an
|
||||
empty string is returned.
|
||||
|
||||
Notes:
|
||||
By default, this becomes a string (#dbref) attached to the object's name.
|
||||
|
||||
"""
|
||||
if looker and self.locks.check_lockstring(looker, "perm(Builder)"):
|
||||
return f"(#{self.id})"
|
||||
return ""
|
||||
|
||||
def get_numbered_name(self, count, looker, **kwargs):
|
||||
"""
|
||||
Return the numbered (singular, plural) forms of this object's key. This is by default called
|
||||
|
|
@ -1453,8 +1481,10 @@ class DefaultObject(ObjectDB, metaclass=TypeclassBase):
|
|||
looker (Object): Onlooker. Not used by default.
|
||||
|
||||
Keyword Args:
|
||||
key (str): Optional key to pluralize. If not given, the object's `.name` property is
|
||||
used.
|
||||
key (str): Optional key to pluralize. If not given, the object's `.get_display_name()`
|
||||
method is used.
|
||||
return_string (bool): If `True`, return only the singular form if count is 0,1 or
|
||||
the plural form otherwise. If `False` (default), return both forms as a tuple.
|
||||
|
||||
Returns:
|
||||
tuple: This is a tuple `(str, str)` with the singular and plural forms of the key
|
||||
|
|
@ -1466,7 +1496,8 @@ class DefaultObject(ObjectDB, metaclass=TypeclassBase):
|
|||
|
||||
"""
|
||||
plural_category = "plural_key"
|
||||
key = kwargs.get("key", self.name)
|
||||
key = kwargs.get("key", self.get_display_name(looker))
|
||||
raw_key = self.name
|
||||
key = ansi.ANSIString(key) # this is needed to allow inflection of colored names
|
||||
try:
|
||||
plural = _INFLECT.plural(key, count)
|
||||
|
|
@ -1482,6 +1513,10 @@ class DefaultObject(ObjectDB, metaclass=TypeclassBase):
|
|||
# save the singular form as an alias here too so we can display "an egg" and also
|
||||
# look at 'an egg'.
|
||||
self.aliases.add(singular, category=plural_category)
|
||||
|
||||
if kwargs.get("return_string"):
|
||||
return singular if count in (0, 1) else plural
|
||||
|
||||
return singular, plural
|
||||
|
||||
def get_display_header(self, looker, **kwargs):
|
||||
|
|
@ -1645,6 +1680,7 @@ class DefaultObject(ObjectDB, metaclass=TypeclassBase):
|
|||
return self.format_appearance(
|
||||
self.appearance_template.format(
|
||||
name=self.get_display_name(looker, **kwargs),
|
||||
extra_name_info=self.get_extra_display_name_info(looker, **kwargs),
|
||||
desc=self.get_display_desc(looker, **kwargs),
|
||||
header=self.get_display_header(looker, **kwargs),
|
||||
footer=self.get_display_footer(looker, **kwargs),
|
||||
|
|
|
|||
|
|
@ -11,12 +11,11 @@ from datetime import datetime, timedelta
|
|||
|
||||
import mock
|
||||
from django.test import TestCase
|
||||
from parameterized import parameterized
|
||||
from twisted.internet import task
|
||||
|
||||
from evennia.utils import utils
|
||||
from evennia.utils.ansi import ANSIString
|
||||
from evennia.utils.test_resources import BaseEvenniaTest
|
||||
from parameterized import parameterized
|
||||
from twisted.internet import task
|
||||
|
||||
|
||||
class TestIsIter(TestCase):
|
||||
|
|
@ -775,6 +774,54 @@ class TestJustify(TestCase):
|
|||
self.assertIn(ANSI_RED, str(result))
|
||||
|
||||
|
||||
class TestGroupObjectsByKeyAndDesc(TestCase):
|
||||
"""
|
||||
Test the utils.group_objects_by_key_and_desc function.
|
||||
|
||||
"""
|
||||
|
||||
class MockObject:
|
||||
def __init__(self, key, desc):
|
||||
self.key = key
|
||||
self.desc = desc
|
||||
|
||||
def get_display_name(self, looker, **kwargs):
|
||||
return self.key + f" (looker: {looker.key})"
|
||||
|
||||
def get_display_desc(self, looker, **kwargs):
|
||||
return self.desc + f" (looker: {looker.key})"
|
||||
|
||||
def get_numbered_name(self, count, looker, **kwargs):
|
||||
return f"{count} {self.key} (looker: {looker.key})"
|
||||
|
||||
def __repr__(self):
|
||||
return f"MockObject({self.key}, {self.desc})"
|
||||
|
||||
def test_group_by_key_and_desc(self):
|
||||
ma1 = self.MockObject("itemA", "descA")
|
||||
ma2 = self.MockObject("itemA", "descA")
|
||||
ma3 = self.MockObject("itemA", "descA")
|
||||
ma4 = self.MockObject("itemA", "descA")
|
||||
|
||||
mb1 = self.MockObject("itemB", "descB")
|
||||
mb2 = self.MockObject("itemB", "descB")
|
||||
mb3 = self.MockObject("itemB", "descB")
|
||||
|
||||
me = self.MockObject("Looker", "DescLooker")
|
||||
|
||||
result = utils.group_objects_by_key_and_desc([ma1, ma2, ma3, ma4, mb1, mb2, mb3], caller=me)
|
||||
|
||||
self.assertEqual(
|
||||
list(result),
|
||||
[
|
||||
("4 itemA (looker: Looker)", "descA (looker: Looker)", [ma1, ma2, ma3, ma4]),
|
||||
("3 itemB (looker: Looker)", "descB (looker: Looker)", [mb1, mb2, mb3]),
|
||||
],
|
||||
)
|
||||
|
||||
# Create a list of objects
|
||||
|
||||
|
||||
class TestMatchIP(TestCase):
|
||||
"""
|
||||
test utils.match_ip
|
||||
|
|
|
|||
|
|
@ -28,6 +28,7 @@ from os.path import join as osjoin
|
|||
from string import punctuation
|
||||
from unicodedata import east_asian_width
|
||||
|
||||
import evennia
|
||||
from django.apps import apps
|
||||
from django.conf import settings
|
||||
from django.core.exceptions import ValidationError as DjangoValidationError
|
||||
|
|
@ -35,14 +36,12 @@ from django.core.validators import validate_email as django_validate_email
|
|||
from django.utils import timezone
|
||||
from django.utils.html import strip_tags
|
||||
from django.utils.translation import gettext as _
|
||||
from evennia.utils import logger
|
||||
from simpleeval import simple_eval
|
||||
from twisted.internet import reactor, threads
|
||||
from twisted.internet.defer import returnValue # noqa - used as import target
|
||||
from twisted.internet.task import deferLater
|
||||
|
||||
import evennia
|
||||
from evennia.utils import logger
|
||||
|
||||
_MULTIMATCH_TEMPLATE = settings.SEARCH_MULTIMATCH_TEMPLATE
|
||||
_EVENNIA_DIR = settings.EVENNIA_DIR
|
||||
_GAME_DIR = settings.GAME_DIR
|
||||
|
|
@ -1767,6 +1766,41 @@ def string_partial_matching(alternatives, inp, ret_index=True):
|
|||
return []
|
||||
|
||||
|
||||
def group_objects_by_key_and_desc(objects, caller=None, **kwargs):
|
||||
"""
|
||||
Groups a list of objects by their key and description. This is used to group
|
||||
visibly identical objects together, for example for inventory listings.
|
||||
|
||||
Args:
|
||||
objects (list): A list of objects to group. These must be DefaultObject.
|
||||
|
||||
caller (Object, optional): The object looking at the objects, used to get the
|
||||
description and key of each object.
|
||||
**kwargs: Passed into each object's `get_display_name/desc` methods.
|
||||
|
||||
Returns:
|
||||
iterable: An iterable of tuples, where each tuple is on the form
|
||||
`(numbered_name, description, [objects])`.
|
||||
|
||||
"""
|
||||
key_descs = defaultdict(list)
|
||||
return_string = kwargs.pop("return_string", True)
|
||||
|
||||
for obj in objects:
|
||||
key_descs[
|
||||
(obj.get_display_name(caller, **kwargs), obj.get_display_desc(caller, **kwargs))
|
||||
].append(obj)
|
||||
|
||||
return (
|
||||
(
|
||||
objs[0].get_numbered_name(len(objs), caller, return_string=return_string, **kwargs),
|
||||
desc,
|
||||
objs,
|
||||
)
|
||||
for (key, desc), objs in sorted(key_descs.items(), key=lambda tup: tup[0][0])
|
||||
)
|
||||
|
||||
|
||||
def format_table(table, extra_space=1):
|
||||
"""
|
||||
Format a 2D array of strings into a multi-column table.
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue