diff --git a/CHANGELOG.md b/CHANGELOG.md index 7fa943f9a9..6df071efc2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,29 @@ # Changelog + +## Main branch + +- [Feat][pull3634]: New contrib for in-game `storage` of items in rooms (aMiss-aWry) +- [Fix][pull3626]: Typo in `defense_type` in evadventure tutorial (feyrkh) +- [Docs][pull3576]: Rework doc for [Pycharm howto][doc-pycharm] +- Docs updates: feykrh + +[pull3626]: https://github.com/evennia/evennia/pull/3626 +[pull3676]: https://github.com/evennia/evennia/pull/3676 +[pull3634]: https://github.com/evennia/evennia/pull/3634 +[doc-pycharm]: https://www.evennia.com/docs/latest/Coding/Setting-up-PyCharm.html + +## Evennia 4.4.1 + +Oct 1, 2024 + +- [Fix][issue3629]: Reverting change of default Sqlite3 PRAGMA settings, changing them for + existing database caused corruption of db. For empty db, can still change in + `SQLITE3_PRAGMAS` settings. (Griatch) + +[issue3629]: https://github.com/evennia/evennia/issues/3629 + + ## Evennia 4.4.0 Sep 29, 2024 @@ -15,7 +39,7 @@ with dynamic keys (rather than just relying on typeclass' key) (Griatch) - [Feat][pull3588]: New `DefaultObject` hooks: `at_object_post_creation`, called once after first creation but after any prototypes have been applied, and `at_object_post_spawn(prototype)`, called only after creation/update with a prototype (InspectorCaracal) -- [Fix][pull3494]: Update/clean some Evennia dependencies (0xDEADFED5) +- [Fix][pull3594]: Update/clean some Evennia dependencies (0xDEADFED5) - [Fix][issue3556]: Better error if trying to treat ObjectDB as a typeclass (Griatch) - [Fix][issue3590]: Make `examine` command properly show `strattr` type Attribute values (Griatch) @@ -55,6 +79,7 @@ did not add it to the handler's object (Griatch) [issue3620]: https://github.com/evennia/evennia/issues/3620 [issue3616]: https://github.com/evennia/evennia/issues/3616 [pull3595]: https://github.com/evennia/evennia/pull/3595 +[pull3595]: https://github.com/evennia/evennia/pull/3595 [pull3533]: https://github.com/evennia/evennia/pull/3533 [pull3594]: https://github.com/evennia/evennia/pull/3594 [pull3592]: https://github.com/evennia/evennia/pull/3592 diff --git a/docs/source/Coding/Changelog.md b/docs/source/Coding/Changelog.md index 7fa943f9a9..7ed3c09e34 100644 --- a/docs/source/Coding/Changelog.md +++ b/docs/source/Coding/Changelog.md @@ -1,5 +1,16 @@ # Changelog +## Evennia 4.4.1 + +Oct 1, 2024 + +- [Fix][issue3629]: Reverting change of default Sqlite3 PRAGMA settings, changing them for + existing database caused corruption of db. For empty db, can still change in + `SQLITE3_PRAGMAS` settings. (Griatch) + +[issue3629]: https://github.com/evennia/evennia/issues/3629 + + ## Evennia 4.4.0 Sep 29, 2024 @@ -15,7 +26,7 @@ with dynamic keys (rather than just relying on typeclass' key) (Griatch) - [Feat][pull3588]: New `DefaultObject` hooks: `at_object_post_creation`, called once after first creation but after any prototypes have been applied, and `at_object_post_spawn(prototype)`, called only after creation/update with a prototype (InspectorCaracal) -- [Fix][pull3494]: Update/clean some Evennia dependencies (0xDEADFED5) +- [Fix][pull3594]: Update/clean some Evennia dependencies (0xDEADFED5) - [Fix][issue3556]: Better error if trying to treat ObjectDB as a typeclass (Griatch) - [Fix][issue3590]: Make `examine` command properly show `strattr` type Attribute values (Griatch) @@ -55,6 +66,7 @@ did not add it to the handler's object (Griatch) [issue3620]: https://github.com/evennia/evennia/issues/3620 [issue3616]: https://github.com/evennia/evennia/issues/3616 [pull3595]: https://github.com/evennia/evennia/pull/3595 +[pull3595]: https://github.com/evennia/evennia/pull/3595 [pull3533]: https://github.com/evennia/evennia/pull/3533 [pull3594]: https://github.com/evennia/evennia/pull/3594 [pull3592]: https://github.com/evennia/evennia/pull/3592 diff --git a/docs/source/Coding/Setting-up-PyCharm.md b/docs/source/Coding/Setting-up-PyCharm.md index 262b62b78c..952bf9812a 100644 --- a/docs/source/Coding/Setting-up-PyCharm.md +++ b/docs/source/Coding/Setting-up-PyCharm.md @@ -1,84 +1,125 @@ # Setting up PyCharm with Evennia -[PyCharm](https://www.jetbrains.com/pycharm/) is a Python developer's IDE from Jetbrains available for Windows, Mac and Linux. It is a commercial product but offer free trials, a scaled-down community edition and also generous licenses for OSS projects like Evennia. +[PyCharm](https://www.jetbrains.com/pycharm/) is a Python developer's IDE from Jetbrains available for Windows, Mac and Linux. +It is a commercial product but offer free trials, a scaled-down community edition and also generous licenses for OSS projects like Evennia. -> This page was originally tested on Windows (so use Windows-style path examples), but should work the same for all platforms. +First, download and install the IDE edition of your choosing. +The community edition should have everything you need, +but the professional edition has integrated support for Django which can help. -First, install Evennia on your local machine with [[Getting Started]]. If you're new to PyCharm, loading your project is as easy as selecting the `Open` option when PyCharm starts, and browsing to your game folder (the one created with `evennia --init`). We refer to it as `mygame` here. +## From an existing project -If you want to be able to examine evennia's core code or the scripts inside your virtualenv, you'll -need to add them to your project too: +Use this if you want to use PyCharm with an already existing Evennia game. +First, ensure you have completed the steps outlined [here](https://www.evennia.com/docs/latest/Setup/Installation.html#requirements). +Especially the virtualenv part, this will make setting the IDE up much easier. -1. Go to `File > Open...` -1. Select the folder (i.e. the `evennia` root) -1. Select "Open in current window" and "Add to currently opened projects" +1. Open Pycharm and click on the open button, open your root folder corresponding to `mygame/`. +2. Click on File -> Settings -> Project -> Python Interpreter -> Add Interpreter -> Add Local Interpreter +![Example](https://imgur.com/QRo8O1C.png) +3. Click on VirtualEnv -> Existing Interpreter -> Select your existing virtualenv folder, + should be `evenv` if you followed the default installation. -It's a good idea to set up the interpreter this before attempting anything further. The rest of this page assumes your project is already configured in PyCharm. +![Example](https://imgur.com/XDmgjTw.png) -1. Go to `File > Settings... > Project: \ > Project Interpreter` -1. Click the Gear symbol `> Add local` -1. Navigate to your `evenv/scripts directory`, and select Python.exe +## From a new project + +Use this if you are starting from scratch or want to make a new Evennia game. +1. Click on the new project button. +2. Select the location for your project. +You should create two new folders, one for the root of your project and one +for the evennia game directly. It should look like `/location/projectfolder/gamefolder` +3. Select the `Custom environment` interpreter type, using `Generate New` of type `Virtual env` using a +compatible base python version as recommended in https://www.evennia.com/docs/latest/Setup/Installation.html#requirements +Then choose a folder for your virtual environment as a sub folder of your project folder. + +![Example new project configuration](https://imgur.com/R5Yr9I4.png) + +Click on the create button and it will take you inside your new project with a bare bones virtual environment. +To install Evennia, you can then either clone evennia in your project folder or install it via pip. +The simplest way is to use pip. + +Click on the `terminal` button + +![Terminal Button](https://i.imgur.com/fDr4nhv.png) + +1. Type in `pip install evennia` +2. Close the IDE and navigate to the project folder +3. Rename the game folder to a temporary name and create a new empty folder with the previous name +4. Open your OS terminal, navigate to your project folder and activate your virtualenv. +On linux, `source .evenv/bin/activate` +On windows, `evenv\Scripts\activate` +5. Type in `evennia --init mygame` +6. Move the files from your temporary folder, which should contain the `.idea/` folder into +the folder you have created at step 3 and delete the now empty temporary folder. +7. In the terminal, Move into the folder and type in `evennia migrate` +8. Start evennia to ensure that it works with `evennia start` and stop it with `evennia stop` + +At this point, you can reopen your IDE and it should be functional. +[Look here for additional information](https://www.evennia.com/docs/latest/Setup/Installation.html) -Enjoy seeing all your imports checked properly, setting breakpoints, and live variable watching! ## Debug Evennia from inside PyCharm -1. Launch Evennia in your preferred way (usually from a console/terminal) -1. Open your project in PyCharm -1. In the PyCharm menu, select `Run > Attach to Local Process...` -1. From the list, pick the `twistd` process with the `server.py` parameter (Example: `twistd.exe --nodaemon --logfile=\\server\logs\server.log --python=\\evennia\server\server.py`) +### Attaching to the process +1. Launch Evennia in the pycharm terminal +2. Attempt to start it twice, this will give you the process ID of the server +3. In the PyCharm menu, select `Run > Attach to Process...` +4. From the list, pick the corresponding process id, it should be the `twistd` process with the `server.py` parameter (Example: `twistd.exe --nodaemon --logfile=\\server\logs\server.log --python=\\evennia\server\server.py`) -Of course you can attach to the `portal` process as well. If you want to debug the Evennia launcher +You can attach to the `portal` process as well, if you want to debug the Evennia launcher or runner for some reason (or just learn how they work!), see Run Configuration below. > NOTE: Whenever you reload Evennia, the old Server process will die and a new one start. So when you restart you have to detach from the old and then reattach to the new process that was created. -> To make the process less tedious you can apply a filter in settings to show only the server.py process in the list. To do that navigate to: `Settings/Preferences | Build, Execution, Deployment | Python Debugger` and then in `Attach to process` field put in: `twistd.exe" --nodaemon`. This is an example for windows, I don't have a working mac/linux box. -![Example process filter configuration](https://i.imgur.com/vkSheR8.png) +### Run Evennia with a Run/Debug Configuration -## Run Evennia from inside PyCharm +This configuration allows you to launch Evennia from inside PyCharm. +Besides convenience, it also allows suspending and debugging the evennia_launcher or evennia_runner +at points earlier than you could by running them externally and attaching. +In fact by the time the server and/or portal are running the launcher will have exited already. -This configuration allows you to launch Evennia from inside PyCharm. Besides convenience, it also allows suspending and debugging the evennia_launcher or evennia_runner at points earlier than you could by running them externally and attaching. In fact by the time the server and/or portal are running the launcher will have exited already. +#### On Windows +1. Go to `Run > Edit Configutations...` +2. Click the plus-symbol to add a new configuration and choose Python +3. Add the script: `\\.evenv\Scripts\evennia_launcher.py` (substitute your virtualenv if it's not named `evenv`) +4. Set script parameters to: `start -l` (-l enables console logging) +5. Ensure the chosen interpreter is your virtualenv +6. Set Working directory to your `mygame` folder (not your project folder nor evennia) +7. You can refer to the PyCharm documentation for general info, but you'll want to set at least a config name (like "MyMUD start" or similar). + +A dropdown box holding your new configurations should appear next to your PyCharm run button. +Select it start and press the debug icon to begin debugging. + +#### On Linux +1. Go to `Run > Edit Configutations...` +2. Click the plus-symbol to add a new configuration and choose Python +3. Add the script: `//.evenv/bin/twistd` (substitute your virtualenv if it's not named `evenv`) +4. Set script parameters to: `--python=//.evenv/lib/python3.11/site-packages/evennia/server/server.py --logger=evennia.utils.logger.GetServerLogObserver --pidfile=///server/server.pid --nodaemon` +5. Add an environment variable `DJANGO_SETTINGS_MODULE=server.conf.settings` +6. Ensure the chosen interpreter is your virtualenv +7. Set Working directory to your game folder (not your project folder nor evennia) +8. You can refer to the PyCharm documentation for general info, but you'll want to set at least a config name (like "MyMUD Server" or similar). + +A dropdown box holding your new configurations should appear next to your PyCharm run button. +Select it start and press the debug icon to begin debugging. +Note that this only starts the server process, you can either start the portal manually or set up +the configuration for the portal. The steps are very similar to the ones above. 1. Go to `Run > Edit Configutations...` -1. Click the plus-symbol to add a new configuration and choose Python -1. Add the script: `\\evenv\Scripts\evennia_launcher.py` (substitute your virtualenv if it's not named `evenv`) -1. Set script parameters to: `start -l` (-l enables console logging) -1. Ensure the chosen interpreter is from your virtualenv -1. Set Working directory to your `mygame` folder (not evenv nor evennia) -1. You can refer to the PyCharm documentation for general info, but you'll want to set at least a config name (like "MyMUD start" or similar). +2. Click the plus-symbol to add a new configuration and choose Python +3. Add the script: `//.evenv/bin/twistd` (substitute your virtualenv if it's not named `evenv`) +4. Set script parameters to: `--python=//.evenv/lib/python3.11/site-packages/evennia/server/portal/portal.py --logger=evennia.utils.logger.GetServerLogObserver --pidfile=///server/portal.pid --nodaemon` +5. Add an environment variable `DJANGO_SETTINGS_MODULE=server.conf.settings` +6. Ensure the chosen interpreter is your virtualenv +7. Set Working directory to your game folder (not your project folder nor evennia) +8. You can refer to the PyCharm documentation for general info, but you'll want to set at least a config name (like "MyMUD Portal" or similar). -Now set up a "stop" configuration by following the same steps as above, but set your Script parameters to: stop (and name the configuration appropriately). +You should now be able to start both modes and get full debugging. +If you want to go one step further, you can add another config to automatically start both. -A dropdown box holding your new configurations should appear next to your PyCharm run button. Select MyMUD start and press the debug icon to begin debugging. Depending on how far you let the program run, you may need to run your "MyMUD stop" config to actually stop the server, before you'll be able start it again. +1. Go to `Run > Edit Configutations...` +2. Click the plus-symbol to add a new configuration and choose Compound +3. Add your two previous configurations, name it appropriately and press Ok. -### Alternative config - utilizing logfiles as source of data - -This configuration takes a bit different approach as instead of focusing on getting the data back through logfiles. Reason for that is this way you can easily separate data streams, for example you rarely want to follow both server and portal at the same time, and this will allow it. This will also make sure to stop the evennia before starting it, essentially working as reload command (it will also include instructions how to disable that part of functionality). We will start by defining a configuration that will stop evennia. This assumes that `upfire` is your pycharm project name, and also the game name, hence the `upfire/upfire` path. - -1. Go to `Run > Edit Configutations...`\ -1. Click the plus-symbol to add a new configuration and choose the python interpreter to use (should be project default) -1. Name the configuration as "stop evennia" and fill rest of the fields accordingly to the image: -![Stop run configuration](https://i.imgur.com/gbkXhlG.png) -1. Press `Apply` - -Now we will define the start/reload command that will make sure that evennia is not running already, and then start the server in one go. -1. Go to `Run > Edit Configutations...`\ -1. Click the plus-symbol to add a new configuration and choose the python interpreter to use (should be project default) -1. Name the configuration as "start evennia" and fill rest of the fields accordingly to the image: -![Start run configuration](https://i.imgur.com/5YEjeHq.png) -1. Navigate to the `Logs` tab and add the log files you would like to follow. The picture shows -adding `portal.log` which will show itself in `portal` tab when running: -![Configuring logs following](https://i.imgur.com/gWYuOWl.png) -1. Skip the following steps if you don't want the launcher to stop evennia before starting. -1. Head back to `Configuration` tab and press the `+` sign at the bottom, under `Before launch....` -and select `Run another configuration` from the submenu that will pop up. -1. Click `stop evennia` and make sure that it's added to the list like on the image above. -1. Click `Apply` and close the run configuration window. - -You are now ready to go, and if you will fire up `start evennia` configuration you should see -following in the bottom panel: -![Example of running alternative configuration](https://i.imgur.com/nTfpC04.png) -and you can click through the tabs to check appropriate logs, or even the console output as it is -still running in interactive mode. \ No newline at end of file +You can now start your game with one click with full debugging active. diff --git a/docs/source/Howtos/Beginner-Tutorial/Part3/Beginner-Tutorial-Equipment.md b/docs/source/Howtos/Beginner-Tutorial/Part3/Beginner-Tutorial-Equipment.md index cb81de616c..08dfd144c1 100644 --- a/docs/source/Howtos/Beginner-Tutorial/Part3/Beginner-Tutorial-Equipment.md +++ b/docs/source/Howtos/Beginner-Tutorial/Part3/Beginner-Tutorial-Equipment.md @@ -402,18 +402,19 @@ class EquipmentHandler: to_backpack = [obj] else: # for others (body, head), just replace whatever's there - replaced = [obj] + to_backpack = [slots[use_slot]] slots[use_slot] = obj for to_backpack_obj in to_backpack: # put stuff in backpack - slots[use_slot].append(to_backpack_obj) + if to_backpack_obj: + slots[WieldLocation.BACKPACK].append(to_backpack_obj) # store new state self._save() ``` -Here we remember that every `EvAdventureObject` has an `inventory_use_slot` property that tells us where it goes. So we just need to move the object to that slot, replacing whatever is in that place from before. Anything we replace goes back to the backpack. +Here we remember that every `EvAdventureObject` has an `inventory_use_slot` property that tells us where it goes. So we just need to move the object to that slot, replacing whatever is in that place from before. Anything we replace goes back to the backpack, as long as it's actually an item and not `None`, in the case where we are moving an item into an empty slot. ## Get everything @@ -612,4 +613,4 @@ _Handlers_ are useful for grouping functionality together. Now that we spent our We also learned to use _hooks_ to tie _Knave_'s custom equipment handling into Evennia. -With `Characters`, `Objects` and now `Equipment` in place, we should be able to move on to character generation - where players get to make their own character! \ No newline at end of file +With `Characters`, `Objects` and now `Equipment` in place, we should be able to move on to character generation - where players get to make their own character! diff --git a/docs/source/Howtos/Beginner-Tutorial/Part3/Beginner-Tutorial-Objects.md b/docs/source/Howtos/Beginner-Tutorial/Part3/Beginner-Tutorial-Objects.md index 449367eb0f..98330c17f1 100644 --- a/docs/source/Howtos/Beginner-Tutorial/Part3/Beginner-Tutorial-Objects.md +++ b/docs/source/Howtos/Beginner-Tutorial/Part3/Beginner-Tutorial-Objects.md @@ -248,7 +248,7 @@ class EvAdventureWeapon(EvAdventureObject): quality = AttributeProperty(3, autocreate=False) attack_type = AttributeProperty(Ability.STR, autocreate=False) - defend_type = AttributeProperty(Ability.ARMOR, autocreate=False) + defense_type = AttributeProperty(Ability.ARMOR, autocreate=False) damage_roll = AttributeProperty("1d6", autocreate=False) @@ -387,7 +387,7 @@ class EvAdventureRuneStone(EvAdventureWeapon, EvAdventureConsumable): quality = AttributeProperty(3, autocreate=False) attack_type = AttributeProperty(Ability.INT, autocreate=False) - defend_type = AttributeProperty(Ability.DEX, autocreate=False) + defense_type = AttributeProperty(Ability.DEX, autocreate=False) damage_roll = AttributeProperty("1d8", autocreate=False) @@ -488,4 +488,4 @@ Well, we just figured out all we need! You can go back and update `get_obj_stats When you change this function you must also update the related unit test - so your existing test becomes a nice way to test your new Objects as well! Add more tests showing the output of feeding different object-types to `get_obj_stats`. -Try it out yourself. If you need help, a finished utility example is found in [evennia/contrib/tutorials/evadventure/utils.py](get_obj_stats). \ No newline at end of file +Try it out yourself. If you need help, a finished utility example is found in [evennia/contrib/tutorials/evadventure/utils.py](get_obj_stats). diff --git a/docs/source/Setup/Settings-Default.md b/docs/source/Setup/Settings-Default.md index 494bbd614e..f7f0dc29c1 100644 --- a/docs/source/Setup/Settings-Default.md +++ b/docs/source/Setup/Settings-Default.md @@ -320,13 +320,13 @@ DATABASES = { } } # PRAGMA (directives) for the default Sqlite3 database operations. This can be used to tweak -# performance for your setup. Don't change this unless you know what # you are doing. +# performance for your setup. Don't change this unless you know what you are doing. Also +# be careful to change for an already populated database. SQLITE3_PRAGMAS = ( "PRAGMA cache_size=10000", - "PRAGMA synchronous=1", + "PRAGMA synchronous=OFF", "PRAGMA count_changes=OFF", "PRAGMA temp_store=2", - "PRAGMA journal_mode=WAL", ) # How long the django-database connection should be kept open, in seconds. diff --git a/docs/source/index.md b/docs/source/index.md index aa80204128..0a55d908c0 100644 --- a/docs/source/index.md +++ b/docs/source/index.md @@ -1,6 +1,6 @@ # Evennia Documentation -This is the manual of [Evennia](https://www.evennia.com), the open source Python `MU*` creation system. Use the Search bar on the left to find or discover interesting articles. This manual was last updated September 29, 2024, see the [Evennia Changelog](Coding/Changelog.md). Latest released Evennia version is 4.4.0. +This is the manual of [Evennia](https://www.evennia.com), the open source Python `MU*` creation system. Use the Search bar on the left to find or discover interesting articles. This manual was last updated October 01, 2024, see the [Evennia Changelog](Coding/Changelog.md). Latest released Evennia version is 4.4.1. - [Introduction](./Evennia-Introduction.md) - what is this Evennia thing? - [Evennia in Pictures](./Evennia-In-Pictures.md) - a visual overview of Evennia diff --git a/evennia/VERSION.txt b/evennia/VERSION.txt index fdc6698807..cca25a93cd 100644 --- a/evennia/VERSION.txt +++ b/evennia/VERSION.txt @@ -1 +1 @@ -4.4.0 +4.4.1 diff --git a/evennia/contrib/game_systems/storage/README.md b/evennia/contrib/game_systems/storage/README.md new file mode 100644 index 0000000000..5b573e2678 --- /dev/null +++ b/evennia/contrib/game_systems/storage/README.md @@ -0,0 +1,37 @@ +# Item Storage + +Contribution by helpme (2024) + +This module allows certain rooms to be marked as storage locations. + +In those rooms, players can `list`, `store`, and `retrieve` items. Storages can be shared or individual. + +## Installation + +This utility adds the storage-related commands. Import the module into your commands and add it to your command set to make it available. + +Specifically, in `mygame/commands/default_cmdsets.py`: + +```python +... +from evennia.contrib.game_systems.storage import StorageCmdSet # <--- + +class CharacterCmdset(default_cmds.Character_CmdSet): + ... + def at_cmdset_creation(self): + ... + self.add(StorageCmdSet) # <--- + +``` + +Then `reload` to make the `list`, `retrieve`, `store`, and `storage` commands available. + +## Usage + +To mark a location as having item storage, use the `storage` command. By default this is a builder-level command. Storage can be shared, which means everyone using the storage can access all items stored there, or individual, which means only the person who stores an item can retrieve it. See `help storage` for further details. + +## Technical info + +This is a tag-based system. Rooms set as storage rooms are tagged with an identifier marking them as shared or not. Items stored in those rooms are tagged with the storage room identifier and, if the storage room is not shared, the character identifier, and then they are removed from the grid i.e. their location is set to `None`. Upon retrieval, items are untagged and moved back to character inventories. + +When a room is unmarked as storage with the `storage` command, all stored objects are untagged and dropped to the room. You should use the `storage` command to create and remove storages, as otherwise stored objects may become lost. \ No newline at end of file diff --git a/evennia/contrib/game_systems/storage/__init__.py b/evennia/contrib/game_systems/storage/__init__.py new file mode 100644 index 0000000000..335ddff65a --- /dev/null +++ b/evennia/contrib/game_systems/storage/__init__.py @@ -0,0 +1,5 @@ +""" +Item storage integration - helpme 2024 +""" + +from .storage import StorageCmdSet # noqa diff --git a/evennia/contrib/game_systems/storage/storage.py b/evennia/contrib/game_systems/storage/storage.py new file mode 100644 index 0000000000..fe97ecee79 --- /dev/null +++ b/evennia/contrib/game_systems/storage/storage.py @@ -0,0 +1,190 @@ +from evennia import CmdSet +from evennia.utils import list_to_string +from evennia.utils.search import search_object_by_tag +from evennia.commands.default.muxcommand import MuxCommand + +SHARED_TAG_PREFIX = "shared" + + +class StorageCommand(MuxCommand): + """ + Shared functionality for storage-related commands + """ + + def at_pre_cmd(self): + """ + Check if the current location is tagged as a storage location + Every stored object is tagged on storage, and untagged on retrieval + + Returns: + bool: True if the command is to be stopped here + """ + if super().at_pre_cmd(): + return True + + self.storage_location_id = self.caller.location.tags.get(category="storage_location") + if not self.storage_location_id: + self.caller.msg(f"You cannot {self.cmdstring} anything here.") + return True + + self.object_tag = ( + SHARED_TAG_PREFIX + if self.storage_location_id.startswith(SHARED_TAG_PREFIX) + else self.caller.pk + ) + self.currently_stored = search_object_by_tag( + self.object_tag, category=self.storage_location_id + ) + + +class CmdStore(StorageCommand): + """ + Store something in a storage location. + + Usage: + store + """ + + key = "store" + locks = "cmd:all()" + help_category = "Storage" + + def func(self): + """ + Find the item in question to store, then store it + """ + caller = self.caller + if not self.args: + self.caller.msg("Store what?") + return + obj = caller.search(self.args.strip(), candidates=caller.contents) + if not obj: + return + + """ + We first check at_pre_move before setting the location to None, in case + anything should stymie its movement. + """ + if obj.at_pre_move(caller.location): + obj.tags.add(self.object_tag, self.storage_location_id) + obj.location = None + caller.msg(f"You store {obj.get_display_name(caller)} here.") + else: + caller.msg(f"You fail to store {obj.get_display_name(caller)} here.") + + +class CmdRetrieve(StorageCommand): + """ + Retrieve something from a storage location. + + Usage: + retrieve + """ + + key = "retrieve" + locks = "cmd:all()" + help_category = "Storage" + + def func(self): + """ + Retrieve the item in question if possible + """ + caller = self.caller + + if not self.args: + self.caller.msg("Retrieve what?") + return + + obj = caller.search(self.args.strip(), candidates=self.currently_stored) + if not obj: + return + + if obj.at_pre_move(caller): + obj.tags.remove(self.object_tag, self.storage_location_id) + caller.msg(f"You retrieve {obj.get_display_name(caller)}.") + else: + caller.msg(f"You fail to retrieve {obj.get_display_name(caller)}.") + + +class CmdList(StorageCommand): + """ + List items in the storage location. + + Usage: + list + """ + + key = "list" + locks = "cmd:all()" + help_category = "Storage" + + def func(self): + """ + List items in the storage + """ + caller = self.caller + if not self.currently_stored: + caller.msg("You find nothing stored here.") + return + caller.msg(f"Stored here:\n{list_to_string(self.currently_stored)}") + + +class CmdStorage(MuxCommand): + """ + Make the current location a storage room, or delete it as a storage and move all stored objects into the room contents. + + Shared storage locations can be used by all interchangeably. + + The default storage identifier will be its primary key in the database, but you can supply a new one in case you want linked storages. + + Usage: + storage [= [storage identifier]] + storage/shared [= [storage identifier]] + storage/delete + """ + + key = "@storage" + locks = "cmd:perm(Builder)" + + def func(self): + """Set the storage location.""" + + caller = self.caller + location = caller.location + current_storage_id = location.tags.get(category="storage_location") + storage_id = self.lhs or location.pk + + if "delete" in self.switches: + if not current_storage_id: + caller.msg("This is not tagged as a storage location.") + return + # Move the stored objects, if any, into the room + currently_stored_here = search_object_by_tag(category=current_storage_id) + for obj in currently_stored_here: + obj.tags.remove(category=current_storage_id) + obj.location = location + caller.msg("You remove the storage capabilities of the room.") + location.tags.remove(current_storage_id, category="storage_location") + return + + if current_storage_id: + caller.msg("This is already a storage location: |wstorage/delete|n to remove the tag.") + return + + new_storage_id = ( + f"{SHARED_TAG_PREFIX if SHARED_TAG_PREFIX in self.switches else ''}{storage_id}" + ) + location.tags.add(new_storage_id, category="storage_location") + caller.msg(f"This is now a storage location with id: {new_storage_id}.") + + +class StorageCmdSet(CmdSet): + """ + CmdSet for all storage-related commands + """ + + def at_cmdset_creation(self): + self.add(CmdStore) + self.add(CmdRetrieve) + self.add(CmdList) + self.add(CmdStorage) diff --git a/evennia/contrib/game_systems/storage/tests.py b/evennia/contrib/game_systems/storage/tests.py new file mode 100644 index 0000000000..bb794007d7 --- /dev/null +++ b/evennia/contrib/game_systems/storage/tests.py @@ -0,0 +1,121 @@ +from evennia.commands.default.tests import BaseEvenniaCommandTest +from evennia.utils.create import create_object + +from . import storage + + +class TestStorage(BaseEvenniaCommandTest): + def setUp(self): + super().setUp() + self.obj1.location = self.char1 + self.room1.tags.add("storage_1", "storage_location") + self.room2.tags.add("shared_storage_2", "storage_location") + + def test_store_and_retrieve(self): + self.call( + storage.CmdStore(), + "", + "Store what?", + caller=self.char1, + ) + self.call( + storage.CmdStore(), + "obj", + f"You store {self.obj1.get_display_name(self.char1)} here.", + caller=self.char1, + ) + self.call( + storage.CmdList(), + "", + f"Stored here:\n{self.obj1.get_display_name(self.char1)}", + caller=self.char1, + ) + self.call( + storage.CmdRetrieve(), + "obj2", + "Could not find 'obj2'.", + caller=self.char1, + ) + self.call( + storage.CmdRetrieve(), + "obj", + f"You retrieve {self.obj1.get_display_name(self.char1)}.", + caller=self.char1, + ) + + def test_store_retrieve_while_not_in_storeroom(self): + self.char2.location = self.char1 + self.call(storage.CmdStore(), "obj", "You cannot store anything here.", caller=self.char2) + self.call( + storage.CmdRetrieve(), "obj", "You cannot retrieve anything here.", caller=self.char2 + ) + + def test_store_retrieve_nonexistent_obj(self): + self.call(storage.CmdStore(), "asdasd", "Could not find 'asdasd'.", caller=self.char1) + self.call(storage.CmdRetrieve(), "asdasd", "Could not find 'asdasd'.", caller=self.char1) + + def test_list_nothing_stored(self): + self.call( + storage.CmdList(), + "", + "You find nothing stored here.", + caller=self.char1, + ) + + def test_shared_storage(self): + self.char1.location = self.room2 + self.char2.location = self.room2 + + self.call( + storage.CmdStore(), + "obj", + f"You store {self.obj1.get_display_name(self.char1)} here.", + caller=self.char1, + ) + self.call( + storage.CmdRetrieve(), + "obj", + f"You retrieve {self.obj1.get_display_name(self.char1)}.", + caller=self.char2, + ) + + def test_remove_add_storage(self): + self.char1.permissions.add("builder") + self.call( + storage.CmdStorage(), + "", + "This is already a storage location: storage/delete to remove the tag.", + caller=self.char1, + ) + self.call( + storage.CmdStore(), + "obj", + f"You store {self.obj1.get_display_name(self.char1)} here.", + caller=self.char1, + ) + self.assertEqual(self.obj1.location, None) + self.call( + storage.CmdStorage(), + "/delete", + "You remove the storage capabilities of the room.", + caller=self.char1, + ) + self.assertEqual(self.obj1.location, self.room1) + self.call( + storage.CmdStorage(), + "", + f"This is now a storage location with id: {self.room1.id}.", + caller=self.char1, + ) + self.call( + storage.CmdStorage(), + "/delete", + "You remove the storage capabilities of the room.", + caller=self.char1, + ) + self.call( + storage.CmdStorage(), + "/shared", + f"This is now a storage location with id: shared{self.room1.id}.", + caller=self.char1, + ) diff --git a/evennia/contrib/tutorials/evadventure/utils.py b/evennia/contrib/tutorials/evadventure/utils.py index 25152c236f..34db91b11b 100644 --- a/evennia/contrib/tutorials/evadventure/utils.py +++ b/evennia/contrib/tutorials/evadventure/utils.py @@ -37,7 +37,7 @@ def get_obj_stats(obj, owner=None): carried = f", Worn: [{carried.value}]" if carried else "" attack_type = getattr(obj, "attack_type", None) - defense_type = getattr(obj, "attack_type", None) + defense_type = getattr(obj, "defense_type", None) return _OBJ_STATS.format( key=obj.key, diff --git a/evennia/settings_default.py b/evennia/settings_default.py index ccd3bcccc2..16e15c1649 100644 --- a/evennia/settings_default.py +++ b/evennia/settings_default.py @@ -301,13 +301,13 @@ DATABASES = { } } # PRAGMA (directives) for the default Sqlite3 database operations. This can be used to tweak -# performance for your setup. Don't change this unless you know what # you are doing. +# performance for your setup. Don't change this unless you know what you are doing. Also +# be careful to change for an already populated database. SQLITE3_PRAGMAS = ( "PRAGMA cache_size=10000", - "PRAGMA synchronous=1", + "PRAGMA synchronous=OFF", "PRAGMA count_changes=OFF", "PRAGMA temp_store=2", - "PRAGMA journal_mode=WAL", ) # How long the django-database connection should be kept open, in seconds. diff --git a/pyproject.toml b/pyproject.toml index 79b30016a5..bbc6c74a32 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta" [project] name = "evennia" -version = "4.4.0" +version = "4.4.1" maintainers = [{ name = "Griatch", email = "griatch@gmail.com" }] description = "A full-featured toolkit and server for text-based multiplayer games (MUDs, MU*, etc)." requires-python = ">=3.10"