diff --git a/evennia/contrib/extended_room.py b/evennia/contrib/extended_room.py index 6cacca980c..72048cc41b 100644 --- a/evennia/contrib/extended_room.py +++ b/evennia/contrib/extended_room.py @@ -45,13 +45,14 @@ at, without there having to be a database object created for it. The Details are simply stored in a dictionary on the room and if the look command cannot find an object match for a `look ` command it will also look through the available details at the current location -if applicable. An extended `desc` command is used to set details. +if applicable. The `@detail` command is used to change details. 4) Extra commands CmdExtendedLook - look command supporting room details - CmdExtendedDesc - desc command allowing to add seasonal descs and details, + CmdExtendedDesc - desc command allowing to add seasonal descs, + CmdSetDetail - command allowing to manipulate details in this room as well as listing them CmdGameTime - A simple `time` command, displaying the current time and season. @@ -59,7 +60,7 @@ if applicable. An extended `desc` command is used to set details. Installation/testing: -1) Add `CmdExtendedLook`, `CmdExtendedDesc` and `CmdGameTime` to the default `cmdset` +1) Add `CmdExtendedLook`, `CmdExtendedDesc`, `CmdDetail` and `CmdGameTime` to the default `cmdset` (see Wiki for how to do this). 2) `@dig` a room of type `contrib.extended_room.ExtendedRoom` (or make it the default room type) @@ -213,6 +214,42 @@ class ExtendedRoom(DefaultRoom): return detail return None + def set_detail(self, detailkey, description): + """ + This sets a new detail, using an Attribute "details". + + Args: + detailkey (str): The detail identifier to add (for + aliases you need to add multiple keys to the + same description). Case-insensitive. + description (str): The text to return when looking + at the given detailkey. + + """ + if self.db.details: + self.db.details[detailkey.lower()] = description + else: + self.db.details = {detailkey.lower(): description} + + def del_detail(self, detailkey, description): + """ + Delete a detail. + + The description is ignored. + + Args: + detailkey (str): the detail to remove (case-insensitive). + description (str, ignored): the description. + + The description is only included for compliance but is completely + ignored. Note that this method doesn't raise any exception if + the detail doesn't exist in this room. + + """ + if self.db.details and detailkey.lower() in self.db.details: + del self.db.details[detailkey.lower()] + + def return_appearance(self, looker, **kwargs): """ This is called when e.g. the look command wants to retrieve @@ -335,8 +372,6 @@ class CmdExtendedDesc(default_cmds.CmdDesc): Usage: desc[/switch] [ =] - detail[/del] [ = ] - Switches for `desc`: spring - set description for in current room. @@ -344,15 +379,9 @@ class CmdExtendedDesc(default_cmds.CmdDesc): autumn winter - Switch for `detail`: - del - delete a named detail. - Sets the "desc" attribute on an object. If an object is not given, describe the current room. - The alias `detail` allows to assign a "detail" (a non-object - target for the `look` command) to the current room (only). - You can also embed special time markers in your room description, like this: ``` @@ -362,11 +391,11 @@ class CmdExtendedDesc(default_cmds.CmdDesc): Text marked this way will only display when the server is truly at the given timeslot. The available times are night, morning, afternoon and evening. - Note that `detail`, seasons and time-of-day slots only work on rooms in this + Note that seasons and time-of-day slots only work on rooms in this version of the `desc` command. """ - aliases = ["describe", "detail"] + aliases = ["describe"] switch_options = () # Inherits from default_cmds.CmdDesc, but unused here def reset_times(self, obj): @@ -378,92 +407,116 @@ class CmdExtendedDesc(default_cmds.CmdDesc): """Define extended command""" caller = self.caller location = caller.location - if self.cmdname == 'detail': - # switch to detailing mode. This operates only on current location + if not self.args: + if location: + string = "|wDescriptions on %s|n:\n" % location.key + string += " |wspring:|n %s\n" % location.db.spring_desc + string += " |wsummer:|n %s\n" % location.db.summer_desc + string += " |wautumn:|n %s\n" % location.db.autumn_desc + string += " |wwinter:|n %s\n" % location.db.winter_desc + string += " |wgeneral:|n %s" % location.db.general_desc + caller.msg(string) + return + if self.switches and self.switches[0] in ("spring", "summer", "autumn", "winter"): + # a seasonal switch was given + if self.rhs: + caller.msg("Seasonal descs only work with rooms, not objects.") + return + switch = self.switches[0] if not location: - caller.msg("No location to detail!") + caller.msg("No location was found!") return - if location.db.details is None: - caller.msg("|rThis location does not support details.|n") - return - if self.switches and self.switches[0] in 'del': - # removing a detail. - if self.lhs in location.db.details: - del location.db.details[self.lhs] - caller.msg("Detail %s deleted, if it existed." % self.lhs) - self.reset_times(location) - return - if not self.args: - # No args given. Return all details on location - string = "|wDetails on %s|n:" % location - details = "\n".join(" |w%s|n: %s" - % (key, utils.crop(text)) for key, text in location.db.details.items()) - caller.msg("%s\n%s" % (string, details) if details else "%s None." % string) - return - if not self.rhs: - # no '=' used - list content of given detail - if self.args in location.db.details: - string = "|wDetail '%s' on %s:\n|n" % (self.args, location) - string += str(location.db.details[self.args]) - caller.msg(string) - else: - caller.msg("Detail '%s' not found." % self.args) - return - # setting a detail - location.db.details[self.lhs] = self.rhs - caller.msg("Set Detail %s to '%s'." % (self.lhs, self.rhs)) + if switch == 'spring': + location.db.spring_desc = self.args + elif switch == 'summer': + location.db.summer_desc = self.args + elif switch == 'autumn': + location.db.autumn_desc = self.args + elif switch == 'winter': + location.db.winter_desc = self.args + # clear flag to force an update self.reset_times(location) - return + caller.msg("Seasonal description was set on %s." % location.key) else: - # we are doing a desc call - if not self.args: - if location: - string = "|wDescriptions on %s|n:\n" % location.key - string += " |wspring:|n %s\n" % location.db.spring_desc - string += " |wsummer:|n %s\n" % location.db.summer_desc - string += " |wautumn:|n %s\n" % location.db.autumn_desc - string += " |wwinter:|n %s\n" % location.db.winter_desc - string += " |wgeneral:|n %s" % location.db.general_desc - caller.msg(string) + # No seasonal desc set, maybe this is not an extended room + if self.rhs: + text = self.rhs + obj = caller.search(self.lhs) + if not obj: return - if self.switches and self.switches[0] in ("spring", "summer", "autumn", "winter"): - # a seasonal switch was given - if self.rhs: - caller.msg("Seasonal descs only work with rooms, not objects.") - return - switch = self.switches[0] - if not location: - caller.msg("No location was found!") - return - if switch == 'spring': - location.db.spring_desc = self.args - elif switch == 'summer': - location.db.summer_desc = self.args - elif switch == 'autumn': - location.db.autumn_desc = self.args - elif switch == 'winter': - location.db.winter_desc = self.args - # clear flag to force an update - self.reset_times(location) - caller.msg("Seasonal description was set on %s." % location.key) else: - # No seasonal desc set, maybe this is not an extended room - if self.rhs: - text = self.rhs - obj = caller.search(self.lhs) - if not obj: - return - else: - text = self.args - obj = location - obj.db.desc = text # a compatibility fallback - if obj.attributes.has("general_desc"): - obj.db.general_desc = text - self.reset_times(obj) - caller.msg("General description was set on %s." % obj.key) - else: - # this is not an ExtendedRoom. - caller.msg("The description was set on %s." % obj.key) + text = self.args + obj = location + obj.db.desc = text # a compatibility fallback + if obj.attributes.has("general_desc"): + obj.db.general_desc = text + self.reset_times(obj) + caller.msg("General description was set on %s." % obj.key) + else: + # this is not an ExtendedRoom. + caller.msg("The description was set on %s." % obj.key) + + +class CmdSetDetail(default_cmds.MuxCommand): + + """ + sets a detail on a room + + Usage: + @detail[/del] [= ] + @detail ;;... = description + + Example: + @detail + @detail walls = The walls are covered in ... + @detail castle;ruin;tower = The distant ruin ... + @detail/del wall + @detail/del castle;ruin;tower + + This command allows to show the current room details if you enter it + without any argument. Otherwise, sets or deletes a detail on the current + room, if this room supports details like an extended room. To add new + detail, just use the @detail command, specifying the key, an equal sign + and the description. You can assign the same description to several + details using the alias syntax (replace key by alias1;alias2;alias3;...). + To remove one or several details, use the @detail/del switch. + + """ + key = "@detail" + locks = "cmd:perm(Builder)" + help_category = "Building" + + def func(self): + location = self.caller.location + if not self.args: + details = location.db.details + if not details: + self.msg("|rThe room {} doesn't have any detail set.|n".format(location)) + else: + details = sorted(["|y{}|n: {}".format(key, desc) for key, desc in details.items()]) + self.msg("Details on Room:\n" + "\n".join(details)) + return + + if not self.rhs and "del" not in self.switches: + detail = location.return_detail(self.lhs) + if detail: + self.msg("Detail '|y{}|n' on Room:\n{}".format(self.lhs, detail)) + else: + self.msg("Detail '{}' not found.".format(self.lhs)) + return + + method = "set_detail" if "del" not in self.switches else "del_detail" + if not hasattr(location, method): + self.caller.msg("Details cannot be set on %s." % location) + return + for key in self.lhs.split(";"): + # loop over all aliases, if any (if not, this will just be + # the one key to loop over) + getattr(location, method)(key, self.rhs) + if "del" in self.switches: + self.caller.msg("Detail %s deleted, if it existed." % self.lhs) + else: + self.caller.msg("Detail set '%s': '%s'" % (self.lhs, self.rhs)) # Simple command to view the current time and season diff --git a/evennia/contrib/tests.py b/evennia/contrib/tests.py index 1086c8776e..152c29def7 100644 --- a/evennia/contrib/tests.py +++ b/evennia/contrib/tests.py @@ -242,14 +242,24 @@ class TestExtendedRoom(CommandTest): self.call(extended_room.CmdExtendedLook(), "testdetail", self.DETAIL_DESC) self.call(extended_room.CmdExtendedLook(), "nonexistent", "Could not find 'nonexistent'.") - def test_cmdextendeddesc(self): - self.call(extended_room.CmdExtendedDesc(), "", "Details on Room", cmdstring="detail") - self.call(extended_room.CmdExtendedDesc(), "thingie = newdetail with spaces", - "Set Detail thingie to 'newdetail with spaces'.", cmdstring="detail") - self.call(extended_room.CmdExtendedDesc(), "thingie", "Detail 'thingie' on Room:\n", cmdstring="detail") - self.call(extended_room.CmdExtendedDesc(), "/del thingie", "Detail thingie deleted, if it existed.", cmdstring="detail") - self.call(extended_room.CmdExtendedDesc(), "thingie", "Detail 'thingie' not found.", cmdstring="detail") - self.call(extended_room.CmdExtendedDesc(), "", "Descriptions on Room:") + def test_cmdsetdetail(self): + self.call(extended_room.CmdSetDetail(), "", "Details on Room") + self.call(extended_room.CmdSetDetail(), "thingie = newdetail with spaces", + "Detail set 'thingie': 'newdetail with spaces'") + self.call(extended_room.CmdSetDetail(), "thingie", "Detail 'thingie' on Room:\n") + self.call(extended_room.CmdSetDetail(), "/del thingie", "Detail thingie deleted, if it existed.", cmdstring="detail") + self.call(extended_room.CmdSetDetail(), "thingie", "Detail 'thingie' not found.") + + # Test with aliases + self.call(extended_room.CmdSetDetail(), "", "Details on Room") + self.call(extended_room.CmdSetDetail(), "thingie;other;stuff = newdetail with spaces", + "Detail set 'thingie;other;stuff': 'newdetail with spaces'") + self.call(extended_room.CmdSetDetail(), "thingie", "Detail 'thingie' on Room:\n") + self.call(extended_room.CmdSetDetail(), "other", "Detail 'other' on Room:\n") + self.call(extended_room.CmdSetDetail(), "stuff", "Detail 'stuff' on Room:\n") + self.call(extended_room.CmdSetDetail(), "/del other;stuff", "Detail other;stuff deleted, if it existed.") + self.call(extended_room.CmdSetDetail(), "other", "Detail 'other' not found.") + self.call(extended_room.CmdSetDetail(), "stuff", "Detail 'stuff' not found.") def test_cmdgametime(self): self.call(extended_room.CmdGameTime(), "", "It's a spring day, in the evening.") @@ -1596,7 +1606,7 @@ class TestFieldFillFunc(EvenniaTest): def test_field_functions(self): self.assertTrue(fieldfill.form_template_to_dict(FIELD_TEST_TEMPLATE) == FIELD_TEST_DATA) - + # Test of the unixcommand module from evennia.contrib.unixcommand import UnixCommand