From 1bbe86c20f04a73e1b6f056d1866a8f2f279dd31 Mon Sep 17 00:00:00 2001 From: InspectorCaracal Date: Wed, 8 Mar 2023 16:36:43 -0700 Subject: [PATCH 1/9] add containers contrib --- .../contrib/game_systems/containers/README.md | 45 +++ .../game_systems/containers/__init__.py | 1 + .../game_systems/containers/containers.py | 290 ++++++++++++++++++ .../contrib/game_systems/containers/tests.py | 38 +++ 4 files changed, 374 insertions(+) create mode 100644 evennia/contrib/game_systems/containers/README.md create mode 100644 evennia/contrib/game_systems/containers/__init__.py create mode 100644 evennia/contrib/game_systems/containers/containers.py create mode 100644 evennia/contrib/game_systems/containers/tests.py diff --git a/evennia/contrib/game_systems/containers/README.md b/evennia/contrib/game_systems/containers/README.md new file mode 100644 index 0000000000..ef4034feac --- /dev/null +++ b/evennia/contrib/game_systems/containers/README.md @@ -0,0 +1,45 @@ +# Containers +*Contribution by InspectorCaracal (2023)* + +Adds the ability to put objects into other container objects by providing a container typeclass and extending certain base commands. + +## Installation + +To install, import and add the `ContainerCmdSet` to `CharacterCmdSet` in your `default_cmdsets.py` file: + +```python +from evennia.contrib.game_systems.containers import ContainerCmdSet + +class CharacterCmdSet(default_cmds.CharacterCmdSet): + # ... + + def at_cmdset_creation(self): + # ... + self.add(ContainerCmdSet) +``` + +This will replace the default `look` and `get` commands with the container-friendly versions provided by the contrib. + +## Usage + +The contrib includes a `ContainerObject` typeclass which has all of the set-up necessary to be used as a container. To use, all you need to do is create an object in-game with that typeclass - it will automatically inherit anything you implemented in your base Object typeclass as well. + + create bag:evennia.contrib.game_systems.containers.ContainerObject + +To make any other objects usable as containers, all you need to do is set the `get_from` lock type on it. + + lock mysterious box = get_from:true() + +## Extending + +The `ContainerObject` class is intended to be usable as-is, but you can also inherit from it for your own container classes to extend its functionality. Aside from having the container lock pre-set on object creation, it comes with three main additions: + +### `capacity` property + +`ContainerObject.capacity` is an `AttributeProperty` - meaning you can access it in code with `obj.capacity` and also set it in game with `set obj/capacity = 5` - which represents the capacity of the container as an integer. You can override this with a more complex representation of capacity on your own container classes. + +### `at_pre_get_from` and `at_pre_put_in` methods + +These two methods on `ContainerObject` are called as extra checks when attempting to either get an object from, or put an object in, a container. The contrib's `Container.at_pre_get_from` doesn't do any additional validation by default, while `ContainerObject.at_pre_put_in` does a simple capacity check. + +You can override these methods on your own child class to do any additional capacity or access checks. \ No newline at end of file diff --git a/evennia/contrib/game_systems/containers/__init__.py b/evennia/contrib/game_systems/containers/__init__.py new file mode 100644 index 0000000000..ef7887ea31 --- /dev/null +++ b/evennia/contrib/game_systems/containers/__init__.py @@ -0,0 +1 @@ +from .containers import ContainerCmdSet, ContainerObject diff --git a/evennia/contrib/game_systems/containers/containers.py b/evennia/contrib/game_systems/containers/containers.py new file mode 100644 index 0000000000..1440bc1e5a --- /dev/null +++ b/evennia/contrib/game_systems/containers/containers.py @@ -0,0 +1,290 @@ +""" +Containers + +Contribution by InspectorCaracal (2023) + +Adds the ability to put objects into other container objects by providing a container typeclass and extending certain base commands. + +To install, import and add the `ContainerCmdSet` to `CharacterCmdSet` in your `default_cmdsets.py` file: + + from evennia.contrib.game_systems.containers import ContainerCmdSet + + class CharacterCmdSet(default_cmds.CharacterCmdSet): + # ... + + def at_cmdset_creation(self): + # ... + self.add(ContainerCmdSet) + +The ContainerCmdSet includes: + + - a modified `look` command to look at or inside objects + - a modified `get` command to get objects from your location or inside objects + - a new `put` command to put objects from your inventory into other objects + +Create objects with the `ContainerObject` typeclass to easily create containers, +or implement the same locks/hooks in your own typeclasses. + +`ContainerObject` implements the following new methods: + + at_pre_get_from(self, getter, target, **kwargs) - called with the pre-get hooks + at_pre_put_in(self, putter, target, **kwargs) - called with the pre-put hooks +""" +from django.conf import settings + +from evennia import AttributeProperty, CmdSet, DefaultObject +from evennia.commands.default.general import CmdLook, CmdGet, CmdDrop +from evennia.utils import class_from_module + +# establish the right inheritance for container objects +_BASE_OBJECT_TYPECLASS = class_from_module(settings.BASE_OBJECT_TYPECLASS, DefaultObject) + + +class ContainerObject(_BASE_OBJECT_TYPECLASS): + """ + A type of Object which can be used as a container. + + It implements a very basic "size" limitation that is just a flat number of objects. + """ + + # This defines how many objects the container can hold. + capacity = AttributeProperty(default=20) + + def at_object_creation(self): + super().at_object_creation() + # adds a lock permission to allow items to be put inside or taken out + # by default, a lock type not being explicitly set will fail access checks, so normal + # objects without the get_from type set will fail get_from access checks + self.locks.add("get_from:true()") + + def at_pre_get_from(self, getter, target, **kwargs): + """ + This will be called when something attempts to get another object FROM this object, + rather than when getting this object itself. + + Args: + getter (Object): The actor attempting to take something from this object. + target (Object): The thing this object contains that is being removed. + """ + return True + + def at_pre_put_in(self, putter, target, **kwargs): + """ + This will be called when something attempts to get another object FROM this object, + rather than when getting this object itself. + + Args: + getter (Object): The actor attempting to take something from this object. + target (Object): The thing this object contains that is being removed. + + NOTE: + To add more complex capacity checks, modify this method on your child typeclass. + """ + # check if we're already at capacity + if len(self.contents) >= self.capacity: + putter.msg(f"You can't fit anything else in {self.get_numbered_name(1, putter)[0]}.") + return False + + return True + + +class CmdContainerLook(CmdLook): + """ + look at location or object + + Usage: + look + look + look in + look * + + Observes your location or objects in your vicinity. + """ + + rhs_split = (" in ",) + + def func(self): + """ + Handle the looking. + """ + caller = self.caller + # by default, we don't look in anything + container = None + + if not self.args: + target = caller.location + if not target: + self.msg("You have no location to look at!") + return + elif self.rhs: + # we are looking in something, find that first + container = caller.search(self.rhs) + if not container: + return + + target = caller.search(self.lhs, location=container) + if not target: + return + + desc = caller.at_look(target) + # add the type=look to the outputfunc to make it + # easy to separate this output in client. + self.msg(text=(desc, {"type": "look"}), options=None) + + +class CmdContainerGet(CmdGet): + """ + pick up something + + Usage: + get + get from + + Picks up an object from your location or a container and puts it in + your inventory. + """ + + rhs_split = (" from ",) + + def func(self): + caller = self.caller + # by default, we get from the caller's location + location = caller.location + + if not self.args: + self.msg("Get what?") + return + + # check for a container as the location to get from + if self.rhs: + location = caller.search(self.rhs) + if not location: + return + # check access lock + if not location.access(caller, "get_from"): + # supports custom error messages on individual containers + if location.db.get_from_err_msg: + self.msg(location.db.get_from_err_msg) + else: + self.msg("You can't get things from that.") + return + + obj = caller.search(self.lhs, location=location) + if not obj: + return + if caller == obj: + self.msg("You can't get yourself.") + return + if not obj.access(caller, "get"): + if obj.db.get_err_msg: + self.msg(obj.db.get_err_msg) + else: + self.msg("You can't get that.") + return + + # calling possible at_pre_get_from hook on location + if hasattr(location, "at_pre_get_from") and not location.at_pre_get_from(caller, obj): + return + # calling at_pre_get hook method + if not obj.at_pre_get(caller): + return + + success = obj.move_to(caller, quiet=True, move_type="get") + if not success: + self.msg("This can't be picked up.") + else: + singular, _ = obj.get_numbered_name(1, caller) + if location == caller.location: + # we're picking it up from the area + caller.location.msg_contents(f"$You() $conj(pick) up {singular}.", from_obj=caller) + else: + # we're getting it from somewhere else + container_name, _ = location.get_numbered_name(1, caller) + caller.location.msg_contents( + f"$You() $conj(get) {singular} from {container_name}.", from_obj=caller + ) + # calling at_get hook method + obj.at_get(caller) + + +class CmdPut(CmdDrop): + """ + put something down + + Usage: + put in + + Lets you put an object from your inventory into another + object in the vicinity. + """ + + key = "put" + rhs_split = ("=", " in ", " on ") + + def func(self): + caller = self.caller + if not self.args: + self.msg("Put what in where?") + return + + if not self.rhs: + super().func() + return + + obj = caller.search( + self.lhs, + location=caller, + nofound_string=f"You aren't carrying {self.args}.", + multimatch_string=f"You carry more than one {self.args}:", + ) + if not obj: + return + + container = caller.search(self.rhs) + if not container: + return + + # check access lock + if not container.access(caller, "get_from"): + # supports custom error messages on individual containers + if container.db.put_err_msg: + self.msg(container.db.put_err_msg) + else: + self.msg("You can't get things from that.") + return + + # Call the object script's at_pre_drop() method. + if not obj.at_pre_drop(caller): + return + + # Call the container's possible at_pre_put_in method. + if hasattr(container, "at_pre_put_in") and not container.at_pre_put_in(caller, obj): + return + + success = obj.move_to(container, quiet=True, move_type="drop") + if not success: + self.msg("This couldn't be dropped.") + else: + obj_name, _ = obj.get_numbered_name(1, caller) + container_name, _ = container.get_numbered_name(1, caller) + caller.location.msg_contents( + f"$You() $conj(put) {obj_name} in {container_name}.", from_obj=caller + ) + # Call the object script's at_drop() method. + obj.at_drop(caller) + + +class ContainerCmdSet(CmdSet): + """ + Extends the basic `look` and `get` commands to support containers, + and adds an additional `put` command. + """ + + key = "Container CmdSet" + + def at_cmdset_creation(self): + super().at_cmdset_creation() + + self.add(CmdContainerLook) + self.add(CmdContainerGet) + self.add(CmdPut) diff --git a/evennia/contrib/game_systems/containers/tests.py b/evennia/contrib/game_systems/containers/tests.py new file mode 100644 index 0000000000..8d34582215 --- /dev/null +++ b/evennia/contrib/game_systems/containers/tests.py @@ -0,0 +1,38 @@ +from evennia import create_object +from evennia.utils.test_resources import BaseEvenniaTest, BaseEvenniaCommandTest # noqa +from .containers import ContainerObject, CmdContainerGet, CmdContainerLook, CmdPut + + +class TestContainer(BaseEvenniaTest): + def setUp(self): + super().setUp() + # create a container to test with + self.container = create_object(key="Box", typeclass=ContainerObject, location=self.room1) + + def test_capacity(self): + # limit capacity to 1 + self.container.capacity = 1 + self.assertTrue(self.container.at_pre_put_in(self.char1, self.obj1)) + # put Obj2 in container to hit max capacity + self.obj2.location = self.container + self.assertFalse(self.container.at_pre_put_in(self.char1, self.obj1)) + + +class TestContainerCmds(BaseEvenniaCommandTest): + def setUp(self): + super().setUp() + # create a container to test with + self.container = create_object(key="Box", typeclass=ContainerObject, location=self.room1) + + def test_look_in(self): + # make sure the object is in the container so we can look at it + self.obj1.location = self.container + self.call(CmdContainerLook(), "obj in box", "Obj") + + def test_get_and_put(self): + # 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.") + # get from the container + self.call(CmdContainerGet(), "obj from box", "You get an Obj from a Box.") From 59f79858cb3c88b80e933084a496323331abb383 Mon Sep 17 00:00:00 2001 From: InspectorCaracal Date: Wed, 8 Mar 2023 19:42:31 -0700 Subject: [PATCH 2/9] minor docs update --- evennia/contrib/game_systems/containers/README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/evennia/contrib/game_systems/containers/README.md b/evennia/contrib/game_systems/containers/README.md index ef4034feac..54834bf379 100644 --- a/evennia/contrib/game_systems/containers/README.md +++ b/evennia/contrib/game_systems/containers/README.md @@ -24,7 +24,7 @@ This will replace the default `look` and `get` commands with the container-frien The contrib includes a `ContainerObject` typeclass which has all of the set-up necessary to be used as a container. To use, all you need to do is create an object in-game with that typeclass - it will automatically inherit anything you implemented in your base Object typeclass as well. - create bag:evennia.contrib.game_systems.containers.ContainerObject + create bag:game_systems.containers.ContainerObject To make any other objects usable as containers, all you need to do is set the `get_from` lock type on it. @@ -40,6 +40,6 @@ The `ContainerObject` class is intended to be usable as-is, but you can also inh ### `at_pre_get_from` and `at_pre_put_in` methods -These two methods on `ContainerObject` are called as extra checks when attempting to either get an object from, or put an object in, a container. The contrib's `Container.at_pre_get_from` doesn't do any additional validation by default, while `ContainerObject.at_pre_put_in` does a simple capacity check. +These two methods on `ContainerObject` are called as extra checks when attempting to either get an object from, or put an object in, a container. The contrib's `ContainerObject.at_pre_get_from` doesn't do any additional validation by default, while `ContainerObject.at_pre_put_in` does a simple capacity check. You can override these methods on your own child class to do any additional capacity or access checks. \ No newline at end of file From 4fd1e1d9b7a5dadca42cc885c251c7fb1e64c96b Mon Sep 17 00:00:00 2001 From: InspectorCaracal Date: Thu, 9 Mar 2023 10:37:12 -0700 Subject: [PATCH 3/9] documentation additions and corrections --- .../contrib/game_systems/containers/README.md | 14 ++++++++++++-- .../game_systems/containers/containers.py | 16 ++++++++-------- 2 files changed, 20 insertions(+), 10 deletions(-) diff --git a/evennia/contrib/game_systems/containers/README.md b/evennia/contrib/game_systems/containers/README.md index 54834bf379..e27f7af1d8 100644 --- a/evennia/contrib/game_systems/containers/README.md +++ b/evennia/contrib/game_systems/containers/README.md @@ -18,7 +18,7 @@ class CharacterCmdSet(default_cmds.CharacterCmdSet): self.add(ContainerCmdSet) ``` -This will replace the default `look` and `get` commands with the container-friendly versions provided by the contrib. +This will replace the default `look` and `get` commands with the container-friendly versions provided by the contrib as well as add a new `put` command. ## Usage @@ -26,7 +26,17 @@ The contrib includes a `ContainerObject` typeclass which has all of the set-up n create bag:game_systems.containers.ContainerObject -To make any other objects usable as containers, all you need to do is set the `get_from` lock type on it. +The contrib's `ContainerObject` comes with a capacity limit of a maximum number of items it can hold. This can be changed per individual object. + +In code: +```py +obj.capacity = 5 +``` +In game: + + set box/capacity = 5 + +You can also make any other objects usable as containers by setting the `get_from` lock type on it. lock mysterious box = get_from:true() diff --git a/evennia/contrib/game_systems/containers/containers.py b/evennia/contrib/game_systems/containers/containers.py index 1440bc1e5a..46e6eee449 100644 --- a/evennia/contrib/game_systems/containers/containers.py +++ b/evennia/contrib/game_systems/containers/containers.py @@ -27,8 +27,8 @@ or implement the same locks/hooks in your own typeclasses. `ContainerObject` implements the following new methods: - at_pre_get_from(self, getter, target, **kwargs) - called with the pre-get hooks - at_pre_put_in(self, putter, target, **kwargs) - called with the pre-put hooks + at_pre_get_from(getter, target, **kwargs) - called with the pre-get hooks + at_pre_put_in(putter, target, **kwargs) - called with the pre-put hooks """ from django.conf import settings @@ -70,19 +70,19 @@ class ContainerObject(_BASE_OBJECT_TYPECLASS): def at_pre_put_in(self, putter, target, **kwargs): """ - This will be called when something attempts to get another object FROM this object, - rather than when getting this object itself. + This will be called when something attempts to put another object into this object. Args: - getter (Object): The actor attempting to take something from this object. - target (Object): The thing this object contains that is being removed. + putter (Object): The actor attempting to put something in this object. + target (Object): The thing being put into this object. NOTE: To add more complex capacity checks, modify this method on your child typeclass. """ # check if we're already at capacity if len(self.contents) >= self.capacity: - putter.msg(f"You can't fit anything else in {self.get_numbered_name(1, putter)[0]}.") + singular, _ = self.get_numbered_name(1, putter) + putter.msg(f"You can't fit anything else in {singular}.") return False return True @@ -209,7 +209,7 @@ class CmdContainerGet(CmdGet): class CmdPut(CmdDrop): """ - put something down + put an object into something else Usage: put in From 7339e0e6309546c6d51370f8b95096783cbb2e4a Mon Sep 17 00:00:00 2001 From: Cal Date: Fri, 17 Mar 2023 11:41:40 -0600 Subject: [PATCH 4/9] docs updates --- .../contrib/game_systems/containers/README.md | 14 +++++------ .../game_systems/containers/__init__.py | 2 +- .../game_systems/containers/containers.py | 25 +++++++++++++------ .../contrib/game_systems/containers/tests.py | 6 ++--- 4 files changed, 29 insertions(+), 18 deletions(-) diff --git a/evennia/contrib/game_systems/containers/README.md b/evennia/contrib/game_systems/containers/README.md index e27f7af1d8..e9e3d91c67 100644 --- a/evennia/contrib/game_systems/containers/README.md +++ b/evennia/contrib/game_systems/containers/README.md @@ -1,5 +1,5 @@ # Containers -*Contribution by InspectorCaracal (2023)* +Contribution by InspectorCaracal (2023) Adds the ability to put objects into other container objects by providing a container typeclass and extending certain base commands. @@ -22,11 +22,11 @@ This will replace the default `look` and `get` commands with the container-frien ## Usage -The contrib includes a `ContainerObject` typeclass which has all of the set-up necessary to be used as a container. To use, all you need to do is create an object in-game with that typeclass - it will automatically inherit anything you implemented in your base Object typeclass as well. +The contrib includes a `ContribContainer` typeclass which has all of the set-up necessary to be used as a container. To use, all you need to do is create an object in-game with that typeclass - it will automatically inherit anything you implemented in your base Object typeclass as well. - create bag:game_systems.containers.ContainerObject + create bag:game_systems.containers.ContribContainer -The contrib's `ContainerObject` comes with a capacity limit of a maximum number of items it can hold. This can be changed per individual object. +The contrib's `ContribContainer` comes with a capacity limit of a maximum number of items it can hold. This can be changed per individual object. In code: ```py @@ -42,14 +42,14 @@ You can also make any other objects usable as containers by setting the `get_fro ## Extending -The `ContainerObject` class is intended to be usable as-is, but you can also inherit from it for your own container classes to extend its functionality. Aside from having the container lock pre-set on object creation, it comes with three main additions: +The `ContribContainer` class is intended to be usable as-is, but you can also inherit from it for your own container classes to extend its functionality. Aside from having the container lock pre-set on object creation, it comes with three main additions: ### `capacity` property -`ContainerObject.capacity` is an `AttributeProperty` - meaning you can access it in code with `obj.capacity` and also set it in game with `set obj/capacity = 5` - which represents the capacity of the container as an integer. You can override this with a more complex representation of capacity on your own container classes. +`ContribContainer.capacity` is an `AttributeProperty` - meaning you can access it in code with `obj.capacity` and also set it in game with `set obj/capacity = 5` - which represents the capacity of the container as an integer. You can override this with a more complex representation of capacity on your own container classes. ### `at_pre_get_from` and `at_pre_put_in` methods -These two methods on `ContainerObject` are called as extra checks when attempting to either get an object from, or put an object in, a container. The contrib's `ContainerObject.at_pre_get_from` doesn't do any additional validation by default, while `ContainerObject.at_pre_put_in` does a simple capacity check. +These two methods on `ContribContainer` are called as extra checks when attempting to either get an object from, or put an object in, a container. The contrib's `ContribContainer.at_pre_get_from` doesn't do any additional validation by default, while `ContribContainer.at_pre_put_in` does a simple capacity check. You can override these methods on your own child class to do any additional capacity or access checks. \ No newline at end of file diff --git a/evennia/contrib/game_systems/containers/__init__.py b/evennia/contrib/game_systems/containers/__init__.py index ef7887ea31..ccfcf28afa 100644 --- a/evennia/contrib/game_systems/containers/__init__.py +++ b/evennia/contrib/game_systems/containers/__init__.py @@ -1 +1 @@ -from .containers import ContainerCmdSet, ContainerObject +from .containers import ContainerCmdSet, ContribContainer # noqa diff --git a/evennia/contrib/game_systems/containers/containers.py b/evennia/contrib/game_systems/containers/containers.py index 46e6eee449..19cbcf76bb 100644 --- a/evennia/contrib/game_systems/containers/containers.py +++ b/evennia/contrib/game_systems/containers/containers.py @@ -22,10 +22,10 @@ The ContainerCmdSet includes: - a modified `get` command to get objects from your location or inside objects - a new `put` command to put objects from your inventory into other objects -Create objects with the `ContainerObject` typeclass to easily create containers, +Create objects with the `ContribContainer` typeclass to easily create containers, or implement the same locks/hooks in your own typeclasses. -`ContainerObject` implements the following new methods: +`ContribContainer` implements the following new methods: at_pre_get_from(getter, target, **kwargs) - called with the pre-get hooks at_pre_put_in(putter, target, **kwargs) - called with the pre-put hooks @@ -40,7 +40,7 @@ from evennia.utils import class_from_module _BASE_OBJECT_TYPECLASS = class_from_module(settings.BASE_OBJECT_TYPECLASS, DefaultObject) -class ContainerObject(_BASE_OBJECT_TYPECLASS): +class ContribContainer(_BASE_OBJECT_TYPECLASS): """ A type of Object which can be used as a container. @@ -51,10 +51,15 @@ class ContainerObject(_BASE_OBJECT_TYPECLASS): capacity = AttributeProperty(default=20) def at_object_creation(self): + """ + Extends the base object `at_object_creation` method by setting the "get_from" lock to "true", + allowing other objects to be put inside and removed from this object. + + By default, a lock type not being explicitly set will fail access checks, so objects without + the new "get_from" access lock will fail the access checks and continue behaving as + non-container objects. + """ super().at_object_creation() - # adds a lock permission to allow items to be put inside or taken out - # by default, a lock type not being explicitly set will fail access checks, so normal - # objects without the get_from type set will fail get_from access checks self.locks.add("get_from:true()") def at_pre_get_from(self, getter, target, **kwargs): @@ -65,6 +70,9 @@ class ContainerObject(_BASE_OBJECT_TYPECLASS): Args: getter (Object): The actor attempting to take something from this object. target (Object): The thing this object contains that is being removed. + + Returns: + boolean: Whether the object `target` should be gotten or not. """ return True @@ -76,7 +84,10 @@ class ContainerObject(_BASE_OBJECT_TYPECLASS): putter (Object): The actor attempting to put something in this object. target (Object): The thing being put into this object. - NOTE: + Returns: + boolean: Whether the object `target` should be put down or not. + + Note: To add more complex capacity checks, modify this method on your child typeclass. """ # check if we're already at capacity diff --git a/evennia/contrib/game_systems/containers/tests.py b/evennia/contrib/game_systems/containers/tests.py index 8d34582215..5057c7695b 100644 --- a/evennia/contrib/game_systems/containers/tests.py +++ b/evennia/contrib/game_systems/containers/tests.py @@ -1,13 +1,13 @@ from evennia import create_object from evennia.utils.test_resources import BaseEvenniaTest, BaseEvenniaCommandTest # noqa -from .containers import ContainerObject, CmdContainerGet, CmdContainerLook, CmdPut +from .containers import ContribContainer, CmdContainerGet, CmdContainerLook, CmdPut class TestContainer(BaseEvenniaTest): def setUp(self): super().setUp() # create a container to test with - self.container = create_object(key="Box", typeclass=ContainerObject, location=self.room1) + self.container = create_object(key="Box", typeclass=ContribContainer, location=self.room1) def test_capacity(self): # limit capacity to 1 @@ -22,7 +22,7 @@ class TestContainerCmds(BaseEvenniaCommandTest): def setUp(self): super().setUp() # create a container to test with - self.container = create_object(key="Box", typeclass=ContainerObject, location=self.room1) + self.container = create_object(key="Box", typeclass=ContribContainer, location=self.room1) def test_look_in(self): # make sure the object is in the container so we can look at it From 3c37fc099c9648cf9c35f0235925a84401506477 Mon Sep 17 00:00:00 2001 From: Cal Date: Wed, 29 Mar 2023 10:48:46 -0600 Subject: [PATCH 5/9] update container docs, test --- .../contrib/game_systems/containers/containers.py | 8 ++++++-- evennia/contrib/game_systems/containers/tests.py | 13 +++++++++++++ 2 files changed, 19 insertions(+), 2 deletions(-) diff --git a/evennia/contrib/game_systems/containers/containers.py b/evennia/contrib/game_systems/containers/containers.py index 19cbcf76bb..8e0c70579e 100644 --- a/evennia/contrib/game_systems/containers/containers.py +++ b/evennia/contrib/game_systems/containers/containers.py @@ -73,6 +73,9 @@ class ContribContainer(_BASE_OBJECT_TYPECLASS): Returns: boolean: Whether the object `target` should be gotten or not. + + Notes: + If this method returns False/None, the getting is cancelled before it is even started. """ return True @@ -87,7 +90,8 @@ class ContribContainer(_BASE_OBJECT_TYPECLASS): Returns: boolean: Whether the object `target` should be put down or not. - Note: + Notes: + If this method returns False/None, the putting is cancelled before it is even started. To add more complex capacity checks, modify this method on your child typeclass. """ # check if we're already at capacity @@ -261,7 +265,7 @@ class CmdPut(CmdDrop): if container.db.put_err_msg: self.msg(container.db.put_err_msg) else: - self.msg("You can't get things from that.") + self.msg("You can't put things in that.") return # Call the object script's at_pre_drop() method. diff --git a/evennia/contrib/game_systems/containers/tests.py b/evennia/contrib/game_systems/containers/tests.py index 5057c7695b..d52022f3c2 100644 --- a/evennia/contrib/game_systems/containers/tests.py +++ b/evennia/contrib/game_systems/containers/tests.py @@ -36,3 +36,16 @@ class TestContainerCmds(BaseEvenniaCommandTest): 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.") + + def test_locked_get_put(self): + # lock container + self.container.locks.add("get_from:false()") + # move object to container to try getting + self.obj1.location = self.container + self.call(CmdContainerGet(), "obj from box", "You can't get things from that.") + # move object to character to try putting + self.obj1.location = self.char1 + self.call(CmdPut(), "obj in box", "You can't put things in that.") + + + \ No newline at end of file From ee5865d58469e7ccb9f6ecd866e36b9646a329b6 Mon Sep 17 00:00:00 2001 From: Andrew Bastien Date: Sat, 6 May 2023 04:07:19 -0400 Subject: [PATCH 6/9] Small fix to the AttributeHandler.all(). Fixes #3160 --- evennia/typeclasses/attributes.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/evennia/typeclasses/attributes.py b/evennia/typeclasses/attributes.py index 9f3dc8a124..7420d00c16 100644 --- a/evennia/typeclasses/attributes.py +++ b/evennia/typeclasses/attributes.py @@ -1390,11 +1390,12 @@ class AttributeHandler: """ self.backend.clear_attributes(category, accessing_obj, default_access) - def all(self, accessing_obj=None, default_access=True): + def all(self, category=None, accessing_obj=None, default_access=True): """ Return all Attribute objects on this object, regardless of category. Args: + category (str, optional): A given category to limit results to. accessing_obj (object, optional): Check the `attrread` lock on each attribute before returning them. If not given, this check is skipped. @@ -1408,6 +1409,8 @@ class AttributeHandler: """ attrs = self.backend.get_all_attributes() + if category: + attrs = [attr for attr in attrs if attr.category == category] if accessing_obj: return [ From 9304c8ea29942ed8ee41f24d85cc54912cc1fca8 Mon Sep 17 00:00:00 2001 From: InspectorCaracal Date: Sat, 6 May 2023 17:42:20 -0600 Subject: [PATCH 7/9] messages, test additions --- .../contrib/game_systems/containers/containers.py | 10 ++++++---- evennia/contrib/game_systems/containers/tests.py | 13 ++++++++++++- 2 files changed, 18 insertions(+), 5 deletions(-) diff --git a/evennia/contrib/game_systems/containers/containers.py b/evennia/contrib/game_systems/containers/containers.py index 8e0c70579e..27024a62ad 100644 --- a/evennia/contrib/game_systems/containers/containers.py +++ b/evennia/contrib/game_systems/containers/containers.py @@ -190,7 +190,9 @@ class CmdContainerGet(CmdGet): if caller == obj: self.msg("You can't get yourself.") return - if not obj.access(caller, "get"): + + # check if this object can be gotten + if not obj.access(caller, "get") or not obj.at_pre_get(caller): if obj.db.get_err_msg: self.msg(obj.db.get_err_msg) else: @@ -199,9 +201,7 @@ class CmdContainerGet(CmdGet): # calling possible at_pre_get_from hook on location if hasattr(location, "at_pre_get_from") and not location.at_pre_get_from(caller, obj): - return - # calling at_pre_get hook method - if not obj.at_pre_get(caller): + self.msg("You can't get that.") return success = obj.move_to(caller, quiet=True, move_type="get") @@ -270,10 +270,12 @@ class CmdPut(CmdDrop): # Call the object script's at_pre_drop() method. if not obj.at_pre_drop(caller): + self.msg("You can't put that down.") return # Call the container's possible at_pre_put_in method. if hasattr(container, "at_pre_put_in") and not container.at_pre_put_in(caller, obj): + self.msg("You can't put that there.") return success = obj.move_to(container, quiet=True, move_type="drop") diff --git a/evennia/contrib/game_systems/containers/tests.py b/evennia/contrib/game_systems/containers/tests.py index d52022f3c2..aab8f631b2 100644 --- a/evennia/contrib/game_systems/containers/tests.py +++ b/evennia/contrib/game_systems/containers/tests.py @@ -28,6 +28,9 @@ class TestContainerCmds(BaseEvenniaCommandTest): # make sure the object is in the container so we can look at it self.obj1.location = self.container self.call(CmdContainerLook(), "obj in box", "Obj") + # move it into a non-container object and look at it there too + self.obj1.location = self.obj2 + self.call(CmdContainerLook(), "obj in obj2", "Obj") def test_get_and_put(self): # get normally @@ -47,5 +50,13 @@ class TestContainerCmds(BaseEvenniaCommandTest): self.obj1.location = self.char1 self.call(CmdPut(), "obj in box", "You can't put things in that.") - + def test_at_capacity_put(self): + # set container capacity + self.container.capacity = 1 + # move object to container to fill capacity + self.obj2.location = self.container + # move object to character to try putting + self.obj1.location = self.char1 + self.call(CmdPut(), "obj in box", "You can't fit anything else in a Box.") + \ No newline at end of file From 24f8ccccbf25ec15ffdf88b5020d227606c37f61 Mon Sep 17 00:00:00 2001 From: Griatch Date: Sun, 7 May 2023 21:02:00 +0200 Subject: [PATCH 8/9] Update changelog --- CHANGELOG.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8bf926bd25..59e9c887c2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,10 @@ # Changelog +## Main branch + +- Contrib: Container typeclass with new commands for storing and retrieving + things inside them (InspectorCaracal) + ## Evennia 1.3.0 Apr 29, 2023 From 3d3bd5e9d73299d46709e85d3f2d8ef472e7c46a Mon Sep 17 00:00:00 2001 From: Griatch Date: Sun, 7 May 2023 22:08:46 +0200 Subject: [PATCH 9/9] Update changelog --- CHANGELOG.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 59e9c887c2..f35d1fd88b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,9 @@ - Contrib: Container typeclass with new commands for storing and retrieving things inside them (InspectorCaracal) +- Fix: The `AttributeHandler.all()` now actually accepts `category=` as + keyword arg, like our docs already claimed it should (Volund) + ## Evennia 1.3.0