From 0d0002837446fe4e7e0cc20f142a5830fad0c64b Mon Sep 17 00:00:00 2001 From: InspectorCaracal <51038201+InspectorCaracal@users.noreply.github.com> Date: Mon, 8 Dec 2025 21:52:22 -0700 Subject: [PATCH 1/7] fix clone num logic --- evennia/objects/objects.py | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/evennia/objects/objects.py b/evennia/objects/objects.py index 61890feca0..1f2bd94021 100644 --- a/evennia/objects/objects.py +++ b/evennia/objects/objects.py @@ -1509,11 +1509,13 @@ class DefaultObject(ObjectDB, metaclass=TypeclassBase): returns the new clone name on the form keyXX """ key = self.key - num = sum( - 1 - for obj in self.location.contents - if obj.key.startswith(key) and obj.key.lstrip(key).isdigit() - ) + num = 1 + if self.location: + num = max(0, *[ + int(obj.key.lstrip(key) or 0) + for obj in self.location.contents + if obj.key.startswith(key) + ])+1 return "%s%03i" % (key, num) new_key = new_key or find_clone_key() From 57b6d35b02cbf9cec4bc9cdbf72c7d632bf706f6 Mon Sep 17 00:00:00 2001 From: Cal Date: Mon, 8 Dec 2025 22:05:16 -0700 Subject: [PATCH 2/7] add unit tests --- evennia/objects/tests.py | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/evennia/objects/tests.py b/evennia/objects/tests.py index a0dbb96d98..01fa988ae3 100644 --- a/evennia/objects/tests.py +++ b/evennia/objects/tests.py @@ -395,6 +395,30 @@ class TestObjectManager(BaseEvenniaTest): self.assertEqual(self.obj1.attributes.get(key="phrase", category="adventure"), "plugh") self.assertEqual(obj2.attributes.get(key="phrase", category="adventure"), "plugh") + def test_copy_object_clone_key(self): + # reset key to avoid overlap with other tests + self.obj1.key = "CopyMe" + copied = self.obj1.copy() + self.assertEqual(copied.key, "CopyMe001") # original was Obj + copied2 = self.obj1.copy() + self.assertEqual(copied2.key, "CopyMe002") # next clone + # verify that it increments based on max existing identifier + # both for skipped numbers... + copied.key = "CopyMe003" + copied3 = self.obj1.copy() + self.assertEqual(copied3.key, "CopyMe004") + copied3.delete() + # ...and for duplicate numbers + copied.key = "CopyMe001" + copied2.key = "CopyMe001" + copied3 = self.obj1.copy() + self.assertEqual(copied3.key, "CopyMe002") + + def test_copy_object_no_location(self): + self.obj1.location = None + # we just want to make sure this doesn't error + self.assertIsNotNone(self.obj1.copy()) + class TestContentHandler(BaseEvenniaTest): "Test the ContentHandler (obj.contents)" From 261ca40aa059c7758fa2c412461d4286ce168481 Mon Sep 17 00:00:00 2001 From: Cal Date: Mon, 8 Dec 2025 22:06:32 -0700 Subject: [PATCH 3/7] better validation --- evennia/objects/objects.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/evennia/objects/objects.py b/evennia/objects/objects.py index 1f2bd94021..dee5d33af3 100644 --- a/evennia/objects/objects.py +++ b/evennia/objects/objects.py @@ -1512,9 +1512,9 @@ class DefaultObject(ObjectDB, metaclass=TypeclassBase): num = 1 if self.location: num = max(0, *[ - int(obj.key.lstrip(key) or 0) + int(obj.key.lstrip(key)) for obj in self.location.contents - if obj.key.startswith(key) + if obj.key.startswith(key) and obj.key.lstrip(key).isdigit() ])+1 return "%s%03i" % (key, num) From bf36163b4222b8850f7b87b90182f426a386f4c2 Mon Sep 17 00:00:00 2001 From: Cal Date: Mon, 8 Dec 2025 22:06:32 -0700 Subject: [PATCH 4/7] clean up comments --- evennia/objects/tests.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/evennia/objects/tests.py b/evennia/objects/tests.py index 01fa988ae3..e14db97907 100644 --- a/evennia/objects/tests.py +++ b/evennia/objects/tests.py @@ -399,9 +399,9 @@ class TestObjectManager(BaseEvenniaTest): # reset key to avoid overlap with other tests self.obj1.key = "CopyMe" copied = self.obj1.copy() - self.assertEqual(copied.key, "CopyMe001") # original was Obj + self.assertEqual(copied.key, "CopyMe001") copied2 = self.obj1.copy() - self.assertEqual(copied2.key, "CopyMe002") # next clone + self.assertEqual(copied2.key, "CopyMe002") # verify that it increments based on max existing identifier # both for skipped numbers... copied.key = "CopyMe003" From 8c895fb1b53b672a357e1543bc512fdbec592899 Mon Sep 17 00:00:00 2001 From: Cal Date: Mon, 8 Dec 2025 22:31:21 -0700 Subject: [PATCH 5/7] better account for empty comprehension --- evennia/objects/objects.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/evennia/objects/objects.py b/evennia/objects/objects.py index dee5d33af3..92bd22c555 100644 --- a/evennia/objects/objects.py +++ b/evennia/objects/objects.py @@ -1511,7 +1511,7 @@ class DefaultObject(ObjectDB, metaclass=TypeclassBase): key = self.key num = 1 if self.location: - num = max(0, *[ + num = max([0]+[ int(obj.key.lstrip(key)) for obj in self.location.contents if obj.key.startswith(key) and obj.key.lstrip(key).isdigit() From d05fa6aeecbc21254f66f971511d5edaeaa65e50 Mon Sep 17 00:00:00 2001 From: Cal Date: Sun, 15 Feb 2026 16:45:43 -0700 Subject: [PATCH 6/7] just rewriting the func at this point --- evennia/objects/objects.py | 19 +++++++++++-------- evennia/objects/tests.py | 21 ++++++++++++++------- 2 files changed, 25 insertions(+), 15 deletions(-) diff --git a/evennia/objects/objects.py b/evennia/objects/objects.py index 92bd22c555..14d64b0323 100644 --- a/evennia/objects/objects.py +++ b/evennia/objects/objects.py @@ -1506,17 +1506,20 @@ class DefaultObject(ObjectDB, metaclass=TypeclassBase): Append 01, 02 etc to obj.key. Checks next higher number in the same location, then adds the next number available - returns the new clone name on the form keyXX + Returns the new clone name on the form keyXX """ key = self.key + if not self.location: + # no location means no clone numbering + return key + suffixes = [ + obj.key.removeprefix(key) + for obj in self.location.contents + ] num = 1 - if self.location: - num = max([0]+[ - int(obj.key.lstrip(key)) - for obj in self.location.contents - if obj.key.startswith(key) and obj.key.lstrip(key).isdigit() - ])+1 - return "%s%03i" % (key, num) + if nums := [int(suffix) for suffix in suffixes if suffix.isdigit()]: + num = max(nums) + 1 + return f"{key}{num:02d}" new_key = new_key or find_clone_key() new_obj = ObjectDB.objects.copy_object(self, new_key=new_key, **kwargs) diff --git a/evennia/objects/tests.py b/evennia/objects/tests.py index e14db97907..0f8b0e208a 100644 --- a/evennia/objects/tests.py +++ b/evennia/objects/tests.py @@ -399,20 +399,27 @@ class TestObjectManager(BaseEvenniaTest): # reset key to avoid overlap with other tests self.obj1.key = "CopyMe" copied = self.obj1.copy() - self.assertEqual(copied.key, "CopyMe001") + self.assertEqual(copied.key, "CopyMe01") copied2 = self.obj1.copy() - self.assertEqual(copied2.key, "CopyMe002") + self.assertEqual(copied2.key, "CopyMe02") # verify that it increments based on max existing identifier # both for skipped numbers... - copied.key = "CopyMe003" + copied.key = "CopyMe03" copied3 = self.obj1.copy() - self.assertEqual(copied3.key, "CopyMe004") + self.assertEqual(copied3.key, "CopyMe04") copied3.delete() # ...and for duplicate numbers - copied.key = "CopyMe001" - copied2.key = "CopyMe001" + copied.key = "CopyMe01" + copied2.key = "CopyMe01" copied3 = self.obj1.copy() - self.assertEqual(copied3.key, "CopyMe002") + self.assertEqual(copied3.key, "CopyMe02") + # and that sharing a partial prefix doesn't count + copied3.delete() + copied.key = "CopyMeMe02" + copied2.key = "CopyMe01" + copied3 = self.obj1.copy() + self.assertEqual(copied3.key, "CopyMe02") + def test_copy_object_no_location(self): self.obj1.location = None From 4690dfaf203cbe2f7cee117fbe8c79755d627ede Mon Sep 17 00:00:00 2001 From: Cal Date: Sun, 15 Feb 2026 16:47:45 -0700 Subject: [PATCH 7/7] one last case just in case --- evennia/objects/tests.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/evennia/objects/tests.py b/evennia/objects/tests.py index 0f8b0e208a..523e32cfa0 100644 --- a/evennia/objects/tests.py +++ b/evennia/objects/tests.py @@ -419,6 +419,10 @@ class TestObjectManager(BaseEvenniaTest): copied2.key = "CopyMe01" copied3 = self.obj1.copy() self.assertEqual(copied3.key, "CopyMe02") + # and that nothing breaks if something in the room doesn't share the prefix + copied3.key = "NotACopy" + copied4 = self.obj1.copy() + self.assertEqual(copied4.key, "CopyMe02") def test_copy_object_no_location(self):