diff --git a/CHANGELOG.md b/CHANGELOG.md index ad891c8ce9..9495322c00 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,13 +11,15 @@ - Using `lunr` search indexing for better `help` matching and suggestions. Also improve the main help command's default listing output. - Added `content_types` indexing to DefaultObject's ContentsHandler. (volund) -- Made most of the networking classes such as Protocols and the SessionHandlers +- Made most of the networking classes such as Protocols and the SessionHandlers replaceable via `settings.py` for modding enthusiasts. (volund) -- The `initial_setup.py` file can now be substituted in `settings.py` to customize +- The `initial_setup.py` file can now be substituted in `settings.py` to customize initial game database state. (volund) - Added new Traits contrib, converted and expanded from Ainneve project. - Added new `requirements_extra.txt` file for easily getting all optional dependencies. -- Change default multimatch syntax from 1-obj, 2-obj to obj-1, obj-2. +- Change default multimatch syntax from 1-obj, 2-obj to obj-1, obj-2. +- Make `object.search` support 'stacks=0' keyword - if ``>0``, the method will return + N identical matches instead of triggering a multi-match error. ### Already in master - Renamed Tutorial classes "Weapon" and "WeaponRack" to "TutorialWeapon" and diff --git a/docs/source/Howto/Starting/Part2/Planning-The-Tutorial-Game.md b/docs/source/Howto/Starting/Part2/Planning-The-Tutorial-Game.md index 82cebc05a7..1d434ea636 100644 --- a/docs/source/Howto/Starting/Part2/Planning-The-Tutorial-Game.md +++ b/docs/source/Howto/Starting/Part2/Planning-The-Tutorial-Game.md @@ -91,7 +91,7 @@ bulletin boards. #### How will the world be built? There are two main ways to handle this: -- Traditionally, from in-game with build-commands: This pretty means builders creating content in their game +- Traditionally, from in-game with build-commands: This means builders creating content in their game client. This has the advantage of not requiring Python skills nor server access. This can often be a quite intuitive way to build since you are sort-of walking around in your creation as you build it. However, the developer (you) must make sure to provide build-commands that are flexible enough for builders to be able to @@ -119,13 +119,13 @@ code (such as what you can do with the `py` command). You can it's suggested that this is accomplished by adding more powerful build-commands for them to use. For our tutorial-game, we will only allow privileged builders to modify the world. The exception is crafting, -where we will allow players to to use in-game commands to create specific, prescribed objects from recipes. +which we will limit to repairing broken items by combining them with other repair-related items. ### Systems #### Do you base your game off an existing RPG system or make up your own? -We will make use of [Open Adventure](http://www.geekguild.com/openadventure/), an 'old school' RRG-system +We will make use of [Open Adventure](http://www.geekguild.com/openadventure/), a simple 'old school' RPG-system that is available for free under the Creative Commons license. We'll only use a subset of the rules from the blue "basic" book. For the sake of keeping down the length of this tutorial we will limit what features we will include: @@ -140,12 +140,11 @@ we will include: #### What are the game mechanics? How do you decide if an action succeeds or fails? Open Adventure's conflict resolution is based on adding a trait (such as Strength) with a random number in -order beat a target. We will emulate this in code. +order to beat a target. We will emulate this in code. -There are no pre-set "skills", all resolution is based on using suitable traits in different combinations. -The computer can't do this decision on-the-fly like a GM could, so we need to encode what is needed -to achieve a certain effect - this will be a set of 'skills'. We will only make the minimum amount of skills -needed to accomplish the actions we want to support. This will mean custom Commands. +Having a "skill" means getting a bonus to that roll for a more narrow action. +Since the computer will need to know exactly what those skills are, we will add them more explicitly than +in the rules, but we will only add the minimum to show off the functionality we need. #### Does the flow of time matter in your game - does night and day change? What about seasons? diff --git a/evennia/objects/objects.py b/evennia/objects/objects.py index ea24b916b0..8985233c2d 100644 --- a/evennia/objects/objects.py +++ b/evennia/objects/objects.py @@ -375,6 +375,7 @@ class DefaultObject(ObjectDB, metaclass=TypeclassBase): nofound_string=None, multimatch_string=None, use_dbref=None, + stacked=0, ): """ Returns an Object matching a search string/condition @@ -430,10 +431,19 @@ class DefaultObject(ObjectDB, metaclass=TypeclassBase): will be treated like a normal string. If `None` (default), the ability to query by #dbref is turned on if `self` has the permission 'Builder' and is turned off otherwise. + stacked (int, optional): If > 0, multimatches will be analyzed to determine if they + only contains identical objects; these are then assumed 'stacked' and no multi-match + error will be generated, instead `stacked` number of matches will be returned. If + `stacked` is larger than number of matches, returns that number of matches. If + the found stack is a mix of objects, return None and handle the multi-match + error depending on the value of `quiet`. Returns: - match (Object, None or list): will return an Object/None if `quiet=False`, - otherwise it will return a list of 0, 1 or more matches. + Object: If finding a match an `quiet=False` + None: If not finding a unique match and `quiet=False`. + list: With 0, 1 or more matching objects if `quiet=True` + list: With 2 or more matching objects if `stacked` is a positive integer and + the matched stack has only object-copies. Notes: To find Accounts, use eg. `evennia.account_search`. If @@ -501,8 +511,29 @@ class DefaultObject(ObjectDB, metaclass=TypeclassBase): use_dbref=use_dbref, ) + nresults = len(results) + if stacked > 0 and nresults > 1: + # handle stacks, disable multimatch errors + nstack = nresults + if not exact: + # we re-run exact match agains one of the matches to + # make sure we were not catching partial matches not belonging + # to the stack + nstack = len(ObjectDB.objects.get_objs_with_key_or_alias( + results[0].key, + exact=True, + candidates=list(results), + typeclasses=[typeclass] if typeclass else None + )) + if nstack == nresults: + # a valid stack, return multiple results + return list(results)[:stacked] + if quiet: + # don't auto-handle error messaging return list(results) + + # handle error messages return _AT_SEARCH_RESULT( results, self, diff --git a/evennia/objects/tests.py b/evennia/objects/tests.py index f6080c23c9..83b7f1feaa 100644 --- a/evennia/objects/tests.py +++ b/evennia/objects/tests.py @@ -74,6 +74,18 @@ class DefaultObjectTest(EvenniaTest): self.assertTrue(self.room1.get_absolute_url()) self.assertTrue("admin" in self.room1.web_get_admin_url()) + def test_search_stacked(self): + "Test searching stacks" + coin1 = DefaultObject.create("coin", location=self.room1)[0] + coin2 = DefaultObject.create("coin", location=self.room1)[0] + colon = DefaultObject.create("colon", location=self.room1)[0] + + # stack + self.assertEqual(self.char1.search("coin", stacked=2), [coin1, coin2]) + self.assertEqual(self.char1.search("coin", stacked=5), [coin1, coin2]) + # partial match to 'colon' - multimatch error since stack is not homogenous + self.assertEqual(self.char1.search("co", stacked=2), None) + class TestObjectManager(EvenniaTest): "Test object manager methods"